module DoorkeeperMongodb module Mixins module MongoMapper module AccessTokenMixin extend ActiveSupport::Concern include Doorkeeper::OAuth::Helpers include Doorkeeper::Models::Expirable include Doorkeeper::Models::Revocable include Doorkeeper::Models::Accessible include Doorkeeper::Models::Scopes included do belongs_to :application, class_name: 'Doorkeeper::Application' validates :token, presence: true, uniqueness: true validates :refresh_token, uniqueness: true, if: :use_refresh_token? # @attr_writer [Boolean, nil] use_refresh_token # indicates the possibility of using refresh token attr_writer :use_refresh_token before_validation :generate_token, on: :create before_validation :generate_refresh_token, on: :create, if: :use_refresh_token? end module ClassMethods # Returns an instance of the Doorkeeper::AccessToken with # specific token value. # # @param token [#to_s] # token value (any object that responds to `#to_s`) # # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil # if there is no record with such token # def by_token(token) where(token: token.to_s).first end # Returns an instance of the Doorkeeper::AccessToken # with specific token value. # # @param refresh_token [#to_s] # refresh token value (any object that responds to `#to_s`) # # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil # if there is no record with such refresh token # def by_refresh_token(refresh_token) where(refresh_token: refresh_token.to_s).first end # Revokes AccessToken records that have not been revoked and associated # with the specific Application and Resource Owner. # # @param application_id [Integer] # ID of the Application # @param resource_owner [ActiveRecord::Base] # instance of the Resource Owner model # def revoke_all_for(application_id, resource_owner) where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil). each(&:revoke) end # Looking for not expired Access Token with a matching set of scopes # that belongs to specific Application and Resource Owner. # # @param application [Doorkeeper::Application] # Application instance # @param resource_owner_or_id [ActiveRecord::Base, Integer] # Resource Owner model instance or it's ID # @param scopes [String, Doorkeeper::OAuth::Scopes] # set of scopes # # @return [Doorkeeper::AccessToken, nil] Access Token instance or # nil if matching record was not found # def matching_token_for(application, resource_owner_or_id, scopes) resource_owner_id = if resource_owner_or_id.respond_to?(:to_key) resource_owner_or_id.id else resource_owner_or_id end token = last_authorized_token_for(application.try(:id), resource_owner_id) if token && scopes_match?(token.scopes, scopes, application.try(:scopes)) token end end # Checks whether the token scopes match the scopes from the parameters or # Application scopes (if present). # # @param token_scopes [#to_s] # set of scopes (any object that responds to `#to_s`) # @param param_scopes [String] # scopes from params # @param app_scopes [String] # Application scopes # # @return [Boolean] true if all scopes and blank or matches # and false in other cases # def scopes_match?(token_scopes, param_scopes, app_scopes) (!token_scopes.present? && !param_scopes.present?) || Doorkeeper::OAuth::Helpers::ScopeChecker.match?( token_scopes.to_s, param_scopes, app_scopes ) end # Looking for not expired AccessToken record with a matching set of # scopes that belongs to specific Application and Resource Owner. # If it doesn't exists - then creates it. # # @param application [Doorkeeper::Application] # Application instance # @param resource_owner_id [ActiveRecord::Base, Integer] # Resource Owner model instance or it's ID # @param scopes [#to_s] # set of scopes (any object that responds to `#to_s`) # @param expires_in [Integer] # token lifetime in seconds # @param use_refresh_token [Boolean] # whether to use the refresh token # # @return [Doorkeeper::AccessToken] existing record or a new one # def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token) if Doorkeeper.configuration.reuse_access_token access_token = matching_token_for(application, resource_owner_id, scopes) if access_token && !access_token.expired? return access_token end end create!( application_id: application.try(:id), resource_owner_id: resource_owner_id, scopes: scopes.to_s, expires_in: expires_in, use_refresh_token: use_refresh_token ) end # Looking for not revoked Access Token record that belongs to specific # Application and Resource Owner. # # @param application_id [Integer] # ID of the Application model instance # @param resource_owner_id [Integer] # ID of the Resource Owner model instance # # @return [Doorkeeper::AccessToken, nil] matching AccessToken object or # nil if nothing was found # def last_authorized_token_for(application_id, resource_owner_id) send(order_method, created_at_desc). where(application_id: application_id, resource_owner_id: resource_owner_id, revoked_at: nil).first end end # Access Token type: Bearer. # @see https://tools.ietf.org/html/rfc6750 # The OAuth 2.0 Authorization Framework: Bearer Token Usage # def token_type 'bearer' end def use_refresh_token? @use_refresh_token ||= false !!@use_refresh_token end # JSON representation of the Access Token instance. # # @return [Hash] hash with token data def as_json(_options = {}) { resource_owner_id: resource_owner_id, scopes: scopes, expires_in_seconds: expires_in_seconds, application: {uid: application.try(:uid)}, created_at: created_at.to_i } end # Indicates whether the token instance have the same credential # as the other Access Token. # # @param access_token [Doorkeeper::AccessToken] other token # # @return [Boolean] true if credentials are same of false in other cases # def same_credential?(access_token) application_id == access_token.application_id && resource_owner_id == access_token.resource_owner_id end # Indicates if token is acceptable for specific scopes. # # @param scopes [Array] scopes # # @return [Boolean] true if record is accessible and includes scopes or # false in other cases # def acceptable?(scopes) accessible? && includes_scope?(*scopes) end private # Generates refresh token with UniqueToken generator. # # @return [String] refresh token value # def generate_refresh_token write_attribute :refresh_token, UniqueToken.generate end # Generates and sets the token value with the # configured Generator class (see Doorkeeper.configuration). # # @return [String] generated token value # # @raise [Doorkeeper::Errors::UnableToGenerateToken] # custom class doesn't implement .generate method # @raise [Doorkeeper::Errors::TokenGeneratorNotFound] # custom class doesn't exist # def generate_token self.created_at ||= Time.now.utc generator = Doorkeeper.configuration.access_token_generator.constantize self.token = generator.generate( resource_owner_id: resource_owner_id, scopes: scopes, application: application, expires_in: expires_in, created_at: created_at ) rescue NoMethodError raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`." rescue NameError raise Doorkeeper::Errors::TokenGeneratorNotFound, "#{generator} not found" end end end end end