Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

sequel support #281

Closed
wants to merge 4 commits into from

4 participants

@mrbrdo

Sequel support (without migrations, but I can provide them). I guess it's at 95%. I'm using it in a project and seems to work fine.
Be aware of jeremyevans/sequel#705 i.e. in Rails apps, plugins must be loaded into Sequel::Model before doorkeeper is loaded.

@mrbrdo

if someone could help me out with this migration (how to make the generator), then I can just update the readme and this should probably be good to merge

@mrbrdo

About the Sequel plugins thing I mentioned, it has been largely mitigated by my PR to sequel-rails (https://github.com/TalentBox/sequel-rails#enabling-plugins).
So, anyone care to comment on this PR?

@tute
Owner

Hi @mrbrdo, thank you very much for your work! We are actually planning on removing ORM specific code out of doorkeeper (see 7826bf8#commitcomment-3800578). Next steps is to define the way of gems can enhance doorkeeper at runtime, and to remove mongo mapper and mongoid support.

Will leave this issue open until we define those, thank you again.

@mrbrdo

Okay that sounds really smart, because it was not possible to avoid code duplication while working on this (without serious refactoring). Until then this branch should be good to use if anyone needs it now. Everything is implemented, only the migration has to be copied manually from the source.
I hope this will motivate you to support sequel after you are done with refactor. It really is a very good ORM :)

@stakach stakach referenced this pull request
Closed

Add Couchbase ORM #314

@tute
Owner

Topic regarding extracting ORM specific code off of doorkeeper: #355

@tute
Owner

@mrbrdo I checked your code and found that tests are not running with Sequel gem. Did you do browser testing, or how did you test?
Thank you!

@mrbrdo

I'm using it on several projects in production. I don't remember how I tested, it was a long time ago, sorry.

@tute
Owner

@mrbrdo thank you for sharing your work. I'll close this issue though in favor of #355, also because it's not mergeable and it doesn't have tests. Thank you again!

@tute tute closed this
@tmaier

#355 got closed but not merged.

@mrbrdo Are you planning to work on this again? This would be great!

@tute
Owner

@tmaier: @jasl made some refactors needed for this kind of features. It's on an open PR, I can't make myself time to truly review yet though, but he is the man currently more related with this.

@jasl
Owner

I have some busy work to do this week, I could merge this PR to my branch in next week

@tute
Owner

I actually meant the refactor PR so we can treat this as a separate repository that interacts with doorkeeper. Planning to move ORM specifics out not in! :) Thanks for your quick response.

@mrbrdo

so what is the status on this @tute ? we are still without sequel support?

@tute
Owner

@jasl, do we already have any ORM-specific repository for doorkeeper 2, as a model to create new ones?

@tute
Owner

Thank you @jasl. @mrbrdo doorkeeper currently has no sequel support AFAIK, and anyone is very welcome to create an extension along the lines of https://github.com/jasl/doorkeeper-orms or https://github.com/advancedcontrol/doorkeeper-couchbase.

@mrbrdo

https://github.com/doorkeeper-gem/doorkeeper/blob/master/lib/doorkeeper/config.rb#L39
https://github.com/doorkeeper-gem/doorkeeper/blob/master/lib/doorkeeper/models/concerns/ownership.rb#L7

Nope. Too bad you did not look over my commits before doing that refactor. It's pretty obvious:
mrbrdo@e912e4d#diff-2dac7b5ba275c2921a0ad8385afea15bL28
And that's not the only issue (e.g. Revocable etc). I'm not sure what was actually changed except moving files around.

@tute
Owner

The refactor facilitates the extension of doorkeeper without modifying it. My goal is that a PR like this becomes it's own gem/repository, as I won't be able to successfully maintain many different ORMs, something the community will do better.

Version 2 with these refactors wasn't released yet, if you can explain further what could be improved before we release it will help. Thanks for your work and input!

@mrbrdo

Well my suggestion is that you look at the commits I made in this PR, that will give you a better idea of what parts are currently not designed well for extension with different frameworks.
I would avoid making any assumptions about the query methods (where, find_by...) and define some kind of adapter interface for that. Also not call anything directly on the Model class, but instead always go through adapter (including find). So for example even if the ORM doesn't have save, update_attribute methods, the adapter can return a wrapper object with those methods, but user can still access the models via his ORM. Don't make assumptions about relationship methods (e.g. application.tokens.build, application.tokens.find). All validations and relationships (belongs_to, has_many) should be defined by adapter and not by core library. You could abstract it and implement in core but I don't see any point in it.

@jasl
Owner

@mrbrdo
https://github.com/doorkeeper-gem/doorkeeper/blob/master/lib/doorkeeper/config.rb#L39 seems is my missing, check it out later(busy playing WoD in these days :joy: ).

I agree you partly that defining a set of interfaces and let ORM-specific implement them is better. But I finally not to do that.

  • Doorkeeper have not defined interfaces yet, defining them also needs to refactor controllers, that would take a great of time and too many work.
  • I think my work is giving a chance to extend Doorkeeper support more ORMs, and keeping it simple. so I decide to not do any abstraction on models, and let them include Mixins manually. The official implementation of these Mixins are design for ActiveRecord-like, if not compatible, not use it, just implements these methods.

It seems has reached the goal, doorkeeper-couchbase is a good sample to prove this.

I have plan to discover Doorkeeper's controllers in future, and back to working on refactor models.

@mrbrdo

Yes I guess that setup_application_owner is the only hard blocker. I understand what you mean, if there is not time to do a proper refactor then I agree with you.

@tute
Owner

@mrbrdo your ideas would let people use doorkeeper in other frameworks, and I'd love to see that in the future. I certainly want to move in that direction, thank you for your more detailed explanation.

For now, people do plug in the gem in frameworks with no rails gems, but they have to indeed monkey patch some sore points.

I am happy to start merging refactors toward this goal, one at a time, backwards compatible whenever possible.

Thank you again!

@mrbrdo

No worries. @tute @jasl don't forget about that setup_application_owner method

@jasl jasl referenced this pull request
Merged

small improve #522

@jasl
Owner

@mrbrdo In PR #522 I moved loading Ownership on initialize Doorkeeper.

@mrbrdo

I don't understand how that is better?
If you force include Ownership into Application I cannot add support for Sequel without monkey-patching. Why don't you just include Ownership in the Application model itself, so I can provide my own Application model without you including stuff into it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 15, 2013
  1. @mrbrdo

    sequel support

    mrbrdo authored
  2. @mrbrdo

    add sequel migration template

    mrbrdo authored
  3. @mrbrdo
Commits on Sep 29, 2013
  1. @mrbrdo
This page is out of date. Refresh to see the latest.
View
2  Gemfile
@@ -26,6 +26,8 @@ when 'mongo_mapper'
gem 'mongo_mapper', '0.12.0'
gem 'bson_ext', '~> 1.7'
+when 'sequel'
+ gem 'sequel'
end
gemspec
View
23 README.md
@@ -18,7 +18,7 @@ The gem is under constant development. It is based in the [version 22 of the OAu
- Ruby 1.9.3 or 2.0.0
- Rails 3.1, 3.2, 4.0
-- ORM ActiveRecord, Mongoid 2, Mongoid 3, MongoMapper
+- ORM ActiveRecord, Mongoid 2, Mongoid 3, MongoMapper, Sequel
## Installation
@@ -56,6 +56,27 @@ Doorkeeper.configure do
end
```
+### Sequel
+
+To use sequel, you have to set the `orm` configuration:
+
+``` ruby
+Doorkeeper.configure do
+ orm :mongoid2 # or :mongoid3, :mongo_mapper
+end
+```
+
+If you need load any plugins into `Sequel::Model`, you must do so before Doorkeeper
+is loaded, e.g. in `config/application.rb`:
+
+```ruby
+Sequel::Model.plugin :timestamps, update_on_create: true
+
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env)
+```
+
#### Mongoid indexes
Make sure you create indexes for doorkeeper models. You can do this either by running `rake db:mongoid:create_indexes` or (if you're using Mongoid 2) by adding `autocreate_indexes: true` to your `config/mongoid.yml`
View
8 lib/doorkeeper.rb
@@ -55,7 +55,13 @@ def self.configured?
end
def self.database_installed?
- [AccessToken, AccessGrant, Application].all? { |model| model.table_exists? }
+ [AccessToken, AccessGrant, Application].all? do |model|
+ if model.respond_to?(:table_exists?)
+ model.table_exists?
+ elsif defined?(Sequel)
+ Sequel::Model.db.tables.include?(model.table_name)
+ end
+ end
end
def self.installed?
View
16 lib/doorkeeper/config.rb
@@ -16,16 +16,24 @@ def self.configuration
end
def self.enable_orm
+ require "doorkeeper/models/sequel/sequel_compat" if @config.orm == :sequel
require "doorkeeper/models/#{@config.orm}/access_grant"
require "doorkeeper/models/#{@config.orm}/access_token"
require "doorkeeper/models/#{@config.orm}/application"
- require 'doorkeeper/models/access_grant'
- require 'doorkeeper/models/access_token'
- require 'doorkeeper/models/application'
+ unless @config.orm == :sequel
+ require 'doorkeeper/models/access_grant'
+ require 'doorkeeper/models/access_token'
+ require 'doorkeeper/models/application'
+ end
end
def self.setup_application_owner
- require File.join(File.dirname(__FILE__), 'models', 'ownership')
+ orm_model = File.join(File.dirname(__FILE__), 'models', @config.orm.to_s, 'ownership')
+ if File.exist?(orm_model)
+ require orm_model
+ else
+ require File.join(File.dirname(__FILE__), 'models', 'ownership')
+ end
Doorkeeper::Application.send :include, Doorkeeper::Models::Ownership
end
View
6 lib/doorkeeper/models/revocable.rb
@@ -2,7 +2,11 @@ module Doorkeeper
module Models
module Revocable
def revoke(clock = DateTime)
- update_column :revoked_at, clock.now
+ if respond_to? :update_column
+ update_column :revoked_at, clock.now
+ else
+ update revoked_at: clock.now
+ end
end
def revoked?
View
36 lib/doorkeeper/models/sequel/access_grant.rb
@@ -0,0 +1,36 @@
+module Doorkeeper
+ class AccessGrant < Sequel::Model(:oauth_access_grants)
+ include Doorkeeper::Models::SequelCompat
+ include Doorkeeper::OAuth::Helpers
+ include Doorkeeper::Models::Expirable
+ include Doorkeeper::Models::Revocable
+ include Doorkeeper::Models::Accessible
+ include Doorkeeper::Models::Scopes
+
+ # ---
+
+ many_to_one :application, :class => "Doorkeeper::Application"
+
+ if ::Rails.version.to_i < 4
+ attr_accessible :resource_owner_id, :application_id, :expires_in, :redirect_uri, :scopes
+ end
+
+ plugin :validation_helpers
+ def validate
+ generate_token if new?
+ super
+ validates_presence [:resource_owner_id, :application_id, :token, :expires_in, :redirect_uri]
+ validates_unique :token
+ end
+
+ def self.authenticate(token)
+ where(:token => token).first
+ end
+
+ private
+
+ def generate_token
+ self.token = UniqueToken.generate
+ end
+ end
+end
View
89 lib/doorkeeper/models/sequel/access_token.rb
@@ -0,0 +1,89 @@
+module Doorkeeper
+ class AccessToken < Sequel::Model(:oauth_access_tokens)
+ include Doorkeeper::Models::SequelCompat
+ include Doorkeeper::OAuth::Helpers
+ include Doorkeeper::Models::Expirable
+ include Doorkeeper::Models::Revocable
+ include Doorkeeper::Models::Accessible
+ include Doorkeeper::Models::Scopes
+
+ def self.delete_all_for(application_id, resource_owner)
+ where(:application_id => application_id,
+ :resource_owner_id => resource_owner.id).delete
+ end
+ private_class_method :delete_all_for
+
+ def self.last_authorized_token_for(application, resource_owner_id)
+ where(:application_id => application.id,
+ :resource_owner_id => resource_owner_id,
+ :revoked_at => nil).
+ reverse_order(:created_at).
+ limit(1).
+ first
+ end
+ private_class_method :last_authorized_token_for
+
+ # ---
+ many_to_one :application, :class => "Doorkeeper::Application"
+
+ plugin :validation_helpers
+ def validate
+ generate_token if new?
+ generate_refresh_token if new? && use_refresh_token?
+ super
+ validates_presence [:application_id, :token]
+ validates_unique :token
+ validates_unique :refresh_token if use_refresh_token?
+ end
+
+ attr_accessor :use_refresh_token
+ if ::Rails.version.to_i < 4
+ attr_accessible :application_id, :resource_owner_id, :expires_in, :scopes, :use_refresh_token
+ end
+
+ def self.authenticate(token)
+ where(:token => token).first
+ end
+
+ def self.by_refresh_token(refresh_token)
+ where(:refresh_token => refresh_token).first
+ end
+
+ def self.revoke_all_for(application_id, resource_owner)
+ delete_all_for(application_id, resource_owner)
+ end
+
+ def self.matching_token_for(application, resource_owner_or_id, scopes)
+ resource_owner_id = resource_owner_or_id.kind_of?(Fixnum) ? resource_owner_or_id : resource_owner_or_id.id
+ token = last_authorized_token_for(application, resource_owner_id)
+ token if token && ScopeChecker.matches?(token.scopes, scopes)
+ end
+
+ def token_type
+ "bearer"
+ end
+
+ def use_refresh_token?
+ self.use_refresh_token
+ end
+
+ def as_json(options={})
+ {
+ :resource_owner_id => self.resource_owner_id,
+ :scopes => self.scopes,
+ :expires_in_seconds => self.expires_in_seconds,
+ :application => { :uid => self.application.uid }
+ }
+ end
+
+ private
+
+ def generate_refresh_token
+ self.refresh_token = UniqueToken.generate
+ end
+
+ def generate_token
+ self.token = UniqueToken.generate
+ end
+ end
+end
View
56 lib/doorkeeper/models/sequel/application.rb
@@ -0,0 +1,56 @@
+module Doorkeeper
+ class Application < Sequel::Model(:oauth_applications)
+ include Doorkeeper::Models::SequelCompat
+ include Doorkeeper::OAuth::Helpers
+
+ one_to_many :authorized_tokens, conditions: { revoked_at: nil }, :class => "Doorkeeper::AccessToken"
+
+ def self.authorized_for(resource_owner)
+ AccessToken.where(:resource_owner_id => resource_owner.id, :revoked_at => nil).map(&:application)
+ end
+
+ # ---
+
+ one_to_many :access_grants, :class => "Doorkeeper::AccessGrant"
+ one_to_many :access_tokens, :class => "Doorkeeper::AccessToken"
+ plugin :association_dependencies, access_grants: :destroy, access_tokens: :destroy
+
+ plugin :validation_helpers
+ def validate
+ if new?
+ generate_uid
+ generate_secret
+ end
+ super
+ validates_presence [:name, :secret, :uid, :redirect_uri]
+ validates_unique :uid
+ RedirectUriValidator.new(attributes: values).validate_each(self, :redirect_uri, redirect_uri)
+ end
+
+ if ::Rails.version.to_i < 4
+ attr_accessible :name, :redirect_uri
+ end
+
+ def self.model_name
+ ActiveModel::Name.new(self, Doorkeeper, 'Application')
+ end
+
+ def self.authenticate(uid, secret)
+ self.where(:uid => uid, :secret => secret).first
+ end
+
+ def self.by_uid(uid)
+ self.where(:uid => uid).first
+ end
+
+ private
+
+ def generate_uid
+ self.uid = UniqueToken.generate
+ end
+
+ def generate_secret
+ self.secret = UniqueToken.generate
+ end
+ end
+end
View
44 lib/doorkeeper/models/sequel/ownership.rb
@@ -0,0 +1,44 @@
+module Doorkeeper
+ module Models
+ module Ownership
+ def validate_owner?
+ Doorkeeper.configuration.confirm_application_owner?
+ end
+
+ def self.included(base)
+ base.class_eval do
+ many_to_one :owner, :polymorphic => true
+ many_to_one :owner, :reciprocal=>self.table_name,
+ :setter=>(proc do |owner|
+ self[:owner_id] = (owner.pk if owner)
+ self[:owner_type] = (owner.class.name if owner)
+ end),
+ :dataset=>(proc do
+ klass = owner_type.constantize
+ klass.where(klass.primary_key=>owner_id)
+ end),
+ :eager_loader=>(proc do |eo|
+ id_map = {}
+ eo[:rows].each do |base_model|
+ base_model.associations[:owner] = nil
+ ((id_map[base_model.owner_type] ||= {})[base_model.owner_id] ||= []) << base_model
+ end
+ id_map.each do |klass_name, id_map|
+ klass = klass_name.constantize
+ klass.where(klass.primary_key=>id_map.keys).all do |owner|
+ id_map[owner.pk].each do |base_model|
+ base_model.associations[:owner] = owner
+ end
+ end
+ end
+ end)
+ end
+ end
+
+ def validate
+ super
+ validates_presence :owner if validate_owner?
+ end
+ end
+ end
+end
View
30 lib/doorkeeper/models/sequel/sequel_compat.rb
@@ -0,0 +1,30 @@
+module Doorkeeper
+ module Models
+ module SequelCompat
+ extend ActiveSupport::Concern
+
+ included do
+ plugin :timestamps, update_on_create: true
+ plugin :association_proxies
+ end
+
+ def to_param
+ id
+ end
+
+ module ClassMethods
+ def create!(*args)
+ create(*args)
+ end
+
+ def find(id)
+ if id.kind_of?(Fixnum) || id.kind_of?(String)
+ self[id.to_i]
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
View
2  lib/generators/doorkeeper/templates/initializer.rb
@@ -1,6 +1,6 @@
Doorkeeper.configure do
# Change the ORM that doorkeeper will use.
- # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
+ # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper, :sequel
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
View
48 lib/generators/doorkeeper/templates/sequel_migration.rb
@@ -0,0 +1,48 @@
+Sequel.migration do
+ change do
+
+ create_table :oauth_applications do
+ primary_key :id
+ String :name, null: false
+ String :uid, null: false
+ String :secret, null: false
+ String :redirect_uri, null: false
+ DateTime :created_at, null: false
+ DateTime :updated_at
+
+ index :uid, unique: true
+ end
+
+ create_table :oauth_access_grants do
+ primary_key :id
+ Integer :resource_owner_id, null: false
+ Integer :application_id, null: false
+ String :token, null: false
+ Integer :expires_in, null: false
+ String :redirect_uri, null: false
+ DateTime :created_at, null: false
+ DateTime :updated_at
+ DateTime :revoked_at
+ String :scopes
+
+ index :token, unique: true
+ end
+
+ create_table :oauth_access_tokens do
+ primary_key :id
+ Integer :resource_owner_id
+ Integer :application_id, null: false
+ String :token, null: false
+ String :refresh_token
+ Integer :expires_in
+ DateTime :revoked_at
+ DateTime :created_at, null: false
+ DateTime :updated_at
+ String :scopes
+
+ index :token, unique: true
+ index :resource_owner_id
+ index :refresh_token, unique: true
+ end
+ end
+end
View
4 spec/dummy/app/models/user.rb
@@ -18,6 +18,10 @@ class User
key :name, String
key :password, String
end
+when :sequel
+ class User < Sequel::Model
+ include Doorkeeper::Models::SequelCompat
+ end
end
class User
View
2  spec/dummy/config/initializers/doorkeeper.rb
@@ -1,6 +1,6 @@
Doorkeeper.configure do
# Change the ORM that doorkeeper will use
- # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
+ # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper, :sequel
orm DOORKEEPER_ORM
# This block will be called to check whether the
Something went wrong with that request. Please try again.