Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

sequel support #281

Closed
wants to merge 4 commits into from

4 participants

Jan Berdajs Tute Costa Tobias L. Maier Jun Jiang
Jan Berdajs

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.

Jan Berdajs

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

Jan Berdajs

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 Costa
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.

Jan Berdajs

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 :)

Stephen von Takach stakach referenced this pull request
Closed

Add Couchbase ORM #314

Tute Costa
Owner

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

Tute Costa
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!

Jan Berdajs

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

Tute Costa
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 Costa tute closed this
Tobias L. Maier

#355 got closed but not merged.

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

Tute Costa
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.

Jun Jiang

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

Tute Costa
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.

Jan Berdajs

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

Tute Costa
Owner

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

Tute Costa
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.

Jan Berdajs

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 Costa
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!

Jan Berdajs

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.

Jun Jiang

@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.

Jan Berdajs

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 Costa
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!

Jan Berdajs

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

Jun Jiang jasl referenced this pull request
Merged

small improve #522

Jun Jiang

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

Jan Berdajs

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. Jan Berdajs

    sequel support

    mrbrdo authored
  2. Jan Berdajs

    add sequel migration template

    mrbrdo authored
  3. Jan Berdajs
Commits on Sep 29, 2013
  1. Jan Berdajs
This page is out of date. Refresh to see the latest.
2  Gemfile
View
@@ -26,6 +26,8 @@ when 'mongo_mapper'
gem 'mongo_mapper', '0.12.0'
gem 'bson_ext', '~> 1.7'
+when 'sequel'
+ gem 'sequel'
end
gemspec
23 README.md
View
@@ -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`
8 lib/doorkeeper.rb
View
@@ -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?
16 lib/doorkeeper/config.rb
View
@@ -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
6 lib/doorkeeper/models/revocable.rb
View
@@ -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?
36 lib/doorkeeper/models/sequel/access_grant.rb
View
@@ -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
89 lib/doorkeeper/models/sequel/access_token.rb
View
@@ -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
56 lib/doorkeeper/models/sequel/application.rb
View
@@ -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
44 lib/doorkeeper/models/sequel/ownership.rb
View
@@ -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
30 lib/doorkeeper/models/sequel/sequel_compat.rb
View
@@ -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
2  lib/generators/doorkeeper/templates/initializer.rb
View
@@ -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.
48 lib/generators/doorkeeper/templates/sequel_migration.rb
View
@@ -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
4 spec/dummy/app/models/user.rb
View
@@ -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
2  spec/dummy/config/initializers/doorkeeper.rb
View
@@ -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.