I'm trying to create a feature in my OAuth2 service where developers can create, read, and destroy scopes for use by their applications that use my service.
In order to do this, I've created a basic Scope model and I want Doorkeeper to update its #optional_scopes / #scopes with whatever scopes a user comes and creates/destroys. (Note: scopes can only be destroyed if they aren't in use.)
Note (TL;DR): This all works perfectly in development, but it isn't working in production on Heroku -- so the crux of the question is really surrounding how to update the instance variables inside of Doorkeeper that are normally set upon the app's initialization.... And if it's possible at all!
I've set the initializer to grab all the scopes in the DB and set them to optional_scopes.
In config/initializers/doorkeeper.rb:
Doorkeeper.configure do
...
default_scopes :public
optional_scopes( *Scope.where.not(name: 'public').map{ |s| s.name } )
...
end
I have a basic controller for my "CRD" of scopes which has a filter to reset the scopes list after one has been created or destroyed:
class ScopesController < ApplicationController
after_action :set_optional_scopes, only: [ :create, :destroy ]
...
def set_optional_scopes
Doorkeeper.configuration.instance_variable_set(
'#optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
end
end
In the view for my linked applications, I have a loop of the scopes which offers the user checkboxes for the scopes.
views/doorkeeper/applications/_form.html.erb:
<% Doorkeeper.configuration.optional_scopes.each do |scope| %>
<%= check_box_tag(
scope,
'true',
application_has_scope?( application, scope.to_s )
) %>
<%= label_tag(
scope,
scope.to_s,
class: 'no-style display-inline-block'
) %>
<br>
<% end %>
Note how I'm calling Doorkeeper.configuration.optional_scopes to populate the checkboxes.
Concerned with this code updating appropriately across Heroku instances, I also overwrote Doorkeeper's self.configuration method from:
module Doorkeeper
...
def self.configuration
#config || (fail MissingConfiguration)
end
...
end
to:
module Doorkeeper
def self.configuration
if #config
# Reset the scopes every time the config is called
#config.instance_variable_set(
'#scopes',
Scope.all.map{ |s| s.name }
)
#config
else
(fail MissingConfiguration)
end
end
end
So, as I said above, this is working well in development. However, in production it fails to update the list of checkboxes, which means that Doorkeeper.configuration.optional_scopes doesn't get appropriately reset after the create action.
Thanks so much for your time and any help!
Okay, well, in the process of writing this, I slowed down and figured out the solution, which was right in front of my nose...
In the override of Doorkeeper's self.configuration method, all I needed to do was reset optional_scopes instead of scopes as scopes gets defined as default_scopes + optional_scopes anyway.
So it looks like this:
def self.configuration
if #config
# Reset the scopes every time the config is called
#config.instance_variable_set(
'#optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
#config
else
(fail MissingConfiguration)
end
end
This caused all my tests to fail due to a NoMethodError for the super class of Doorkeeper::OAuth::Scopes then I realized I needed to rewrite that method to include an elsif for Array. So, here's that method:
module OAuth
class Scopes
def +(other)
if other.is_a? Scopes
self.class.from_array(all + other.all)
elsif other.is_a? Array
self.class.from_array(all + other)
else
super(other)
end
end
end
end
You can see the original here.
I hope all of this helps someone someday!
Related
I have a Rails 3.2 application, and I want to use one database for many clients and one application. So for every model I have created a field called account_id, now I want to add a global scope for filtering the row in the base of the account_id of the logging user(account_id is a session param). So in initialize I have created a file and put these code
module ActiveRecord
# = Active Record Named \Scopes \
module Scoping
module Default
module ClassMethods
def unscoped #:nodoc:
return (block_given? ? relation.scoping { yield } : relation).where(account_id: Thread.current['account_id'].id)
end
def default_scope(scope = {})
scope = Proc.new if block_given?
if scope.kind_of?(Array) || scope.is_a?(Hash)
scope.merge!({:conditions=>{account_id:Thread.current['account_id'].id}})
end
self.default_scopes = default_scopes + [scope]
end
end
end
end
end
If I logged with user account_id=2 all is ok, but if in the same moment I logged on another browser or computer with account_id=3 ...I have many errors and on the log, I have seen that the application use account_id=2 but also account_id=3 at the same time.
Any solution? How I can rewrite default_scope(scope = {})? Other other idea?
Thread.current[] data is not unique per request. I used to have the same problem. By that time I had been using this gem https://github.com/steveklabnik/request_store. Hope it will help or at least give an idea.
I am working right now on a Rails 4.0 application (using Ruby 2.0.0).
I would like to interact with Jenkins using jenkins_api_client gem, from multiple pages of my Rails application.
This gem generally using a #client parameter, which is initialized to contain the credentials and other information of the Jenkins server.
This parameter in initialized using something like this:
#client = JenkinsApi::Client.new(:server_ip => '0.0.0.0',
:username => 'somename', :password => 'secret password')
Once initialized, I would like to access this parameter and run multiple sub-routines on it.
This initialization takes time, and I really want to avoid doing this process every time one of the clients would like to use this gem functionality, such as:
# Get a filtered list of jobs from the server
jobs_to_filter = "^test_job.*"
jobs = #client.job.list(jobs_to_filter)
So, I hope to do this only once- when the rails server starts.
I would like to use this parameter from multiple pages of my app, possibly with threaded solution further down the road (not critical at the moment).
Can anyone recommend how to achieve this?
I'd appreciate an answer which is consistent with Rails convention.
Thanks a lot!
as example you could create something like that:
module JenkinsApi
class Client
class << self
attr_reader :instance, :config
def configure(&block)
#config = OpenStruct.new
block.call #config
end
def instance
#instance ||= JenkinsApi::Client.new #config
end
end
end
end
which allow you write in initializer:
JenkinsApi::Client.configure do |config|
config.server_ip = '0.0.0.0'
config.username = 'somename'
config.password = 'secret password'
end
and then use it like: JenkinsApi::Client.instance.job.list(...
If I have this:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
CanCan works properly but if I remove the logger, it fails:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
end
Any ideas what's going on here with CanCan? or my code? :) thanks
From the fine manual:
If the conditions hash does not give you enough control over defining abilities, you can use a block along with any Ruby code you want.
can :update, Project do |project|
project.groups.include?(user.group)
end
If the block returns true then the user has that :update ability for that project, otherwise he will be denied access. The downside to using a block is that it cannot be used to generate conditions for database queries.
Your first block:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
Will always return a true value because Rails.logger.info 'XXXX' returns "XXXX\n" (info is just a wrapper for add and you have to read the source to see what add returns as it isn't very well documented). Without the Rails.logger.info call, the block returns just:
wall_member.try(:user_id) == current_user.id
and that must be false for you.
I'm thinking about writing an automatic spam protection system (maybe I will write a public gem) for rails.
My concept is to include a helper method in application_controller f.e.:
class ApplicationController < ActionController::Base
automatic_captcha_redirect(:min_time => 30.seconds :limit => 50)
...
end
Then I want to include automatical a before_filter in every controller, which checks, if the current request is via post, put or delete-method.
If the user's last post-request is smaller than :min_time, then the request should be redirected to an captcha-input-page (the posted user-data resides in hidden html fields).
# before_filter :check_spam
def check_spam
if !request.get? && session[:last_manipulation_at]
&& session[:last_manipulation_at] >= DateTime.now - 30.seconds
redirect_to captcha_path
# (doesn't know yet how to handle the post data to
# display in hidden fields in the spam-captcha-form)
end
end
And in captcha.haml
=form_tag
-request.params.each do |key, value|
=hidden_field_tag key, value
=captcha_image
=submit_button_tag
If the user submits the right captcha-word, his data will be posted to the right action.
Do you think thats realizable?
Any critics or suggestions? Or an idea how to realize this behaviour?
EDIT:
this should not pass through all the ActiveRecord stack; can't it be implemented as a middleware hook (Rails Rack)?
Yes, would be a good idea - but I'm not very familiar with rails rack :/
what about file uploads? (you can not store it in a hidden file)
Hm... maybe a check if there is a file in the post? (How could that be realized?)
what about Ajax posting?
Maybe sending back http-status codes (f.e. 503 Service temporary unavailable)
why only POST and not also PUT and DELETE?
corrected this in my question
EDIT:
First structure of processing (as non rack-app - I dont know how to write rack apps):
0) Settings in environment.rb
auto_recaptcha[:limit] = 10
auto_recaptcha[:min_time] = 1.minute
1) User posts data
Check last_manipulation and max. amount of allowed manipultations in application_controller.rb
class ApplicationController < ActionController::Base
before_filter :automatic_captcha_redirect
def automatic_captcha_redirect
session[:last_manipulation_at][:manipultation] = [] unless session[:last_manipulation_at][:manipultation]
# Checks if requests are falling under the specifications for showing captcha
if !request.get?
&& session[:last_manipulation_at][:date] > DateTime.now - auto_recaptcha[:min_time]
&& session[:last_manipulation_at][:manipultation].count < auto_recaptcha[:limit]
# If user answered captcha, verify it
if !verify_captcha(params)
#url = request.url
#params = request.params
render "layouts/captcha.haml"
else
# Add successfull manipulation to counter
session[:last_manipulation_at][:manipultation] << DateTime.now
session[:last_manipulation_at][:date] = DateTime.now
end
end
end
end
captcha.haml
-form_tag #url do
-request.params.each do |key, value|
=hidden_field_tag key, value
=captcha_image
=submit_button_tag
2)
...
...
...
last) Post userdata to the right location
post(params) => users_path # path "/users" with method: post
First, i would like to say that this is a very good ideea of a feature.
My qs/remarks:
this should not pass through all the ActiveRecord stack; can't it be implemented as a middleware hook (Rails Rack)?
what about file uploads? (you can not store it in a hidden file)
what about Ajax posting?
why only POST and not also PUT and DELETE?
Anyway, i would be more interested to see the number of posts in last 5 mins, for example, that the date of the last request. I believe it is more relevant.
One way this could be put together:
Middleware/rails metal component that
monitors the requests and adds the
information to the rack session.
Controller helpers for before_filters
on things that might need captchas
View helpers for displaying the
captchas
You could make the captcha rate adjustable through the args passing mechanism of use
#config/environment.rb
config.middleware.use 'CaptchaMiddleware',:period=>5.minutes,:limit=>50,:captcha_url=>'/captcha'
Also, this should not rely on hidden form fields because a determined bot writer could just change the value they are posting to your server code.
Simple middleware example code(slightly better than a stab in the dark, but still)
class CaptchaMiddleware
def initialize app,options
#app = app
#options=options
end
def update_stats!
#session based,on account of laziness
session[:reqs] ||= []
session[:reqs].reject!{ |request| request < Time.now - #options[:period]}
session[:reqs] << Time.now
end
def over_limit?
session[:reqs].length > #options[:limit]
end
def call env
#env = env
if #env["REQUEST_METHOD"]!='GET'
update_stats!
if over_limit?
return [302,{"Location: #{options[:captcha_url]}"},'']
end
end
#app.call env
end
def session
#env["rack.session"]
end
end
How can I treat important session info as if it were a model?
I've started toying with it, and some of it works. But I'm not sure what I'm missing? Does anyone know a good place to start or where I can find some good examples?
Also, I'm able to use the model to set session variables, but what about getting them from the model as opposed to always using session[:blah]... How can I retrieve them from the model instead?
class Cart
attr_reader :items
def initialize(session)
#session = session
#session[:cart] ||= []
#items ||= session[:cart]
end
def add_rooms(new_rooms,startdate,days)
#remove 0 values
new_rooms.delete_if {|k, v| v == "0" }
rooms = []
new_rooms.each do |k, v|
#rname = Room.find(k)
#night = Available.find_by_room_id(k)
rooms << {:room_id => k, :people => v, :no_days => days, :startdate => startdate}
end
#session[:cart] = rooms
##items = rooms
end
end
There are some gotchas to be taken into account with this approach
Depending on your session storage system, your session may grow too big if the objects you are putting in it are complex. The default store for sessions in Rails 2.3 are cookies so there will definitely be a limit imposed by the browser.
If your model changes, after deploying your app users with old sessions will be presented with objects marshalled from the old model definitions, so they may get unexpected errors.
Ryan Bates has a Railscast on creating a Session Based Model.
+1 on the Railscast by Ryan Bates.
This type of thing works for me;
def set_order_id(i)
#cart_session[:order_id] = i
end
def has_order_id?
#cart_session[:order_id]
end
def order_id
has_order_id?
end
You just need a method that returns the data you want from the session.
def blah
#session[:blah]
end
If you need a lot of these, I would create a plugin that extends Class with a method that works similarly to attr_reader. Something like
session_reader :blah