grape_oauth2 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +18 -0
  5. data/.travis.yml +42 -0
  6. data/Gemfile +23 -0
  7. data/README.md +820 -0
  8. data/Rakefile +11 -0
  9. data/gemfiles/active_record.rb +25 -0
  10. data/gemfiles/mongoid.rb +14 -0
  11. data/gemfiles/sequel.rb +24 -0
  12. data/grape_oauth2.gemspec +27 -0
  13. data/grape_oauth2.png +0 -0
  14. data/lib/grape_oauth2.rb +129 -0
  15. data/lib/grape_oauth2/configuration.rb +143 -0
  16. data/lib/grape_oauth2/configuration/class_accessors.rb +36 -0
  17. data/lib/grape_oauth2/configuration/validation.rb +71 -0
  18. data/lib/grape_oauth2/endpoints/authorize.rb +34 -0
  19. data/lib/grape_oauth2/endpoints/token.rb +72 -0
  20. data/lib/grape_oauth2/gem_version.rb +24 -0
  21. data/lib/grape_oauth2/generators/authorization.rb +44 -0
  22. data/lib/grape_oauth2/generators/base.rb +26 -0
  23. data/lib/grape_oauth2/generators/token.rb +62 -0
  24. data/lib/grape_oauth2/helpers/access_token_helpers.rb +54 -0
  25. data/lib/grape_oauth2/helpers/oauth_params.rb +41 -0
  26. data/lib/grape_oauth2/mixins/active_record/access_grant.rb +47 -0
  27. data/lib/grape_oauth2/mixins/active_record/access_token.rb +75 -0
  28. data/lib/grape_oauth2/mixins/active_record/client.rb +35 -0
  29. data/lib/grape_oauth2/mixins/mongoid/access_grant.rb +58 -0
  30. data/lib/grape_oauth2/mixins/mongoid/access_token.rb +88 -0
  31. data/lib/grape_oauth2/mixins/mongoid/client.rb +41 -0
  32. data/lib/grape_oauth2/mixins/sequel/access_grant.rb +68 -0
  33. data/lib/grape_oauth2/mixins/sequel/access_token.rb +86 -0
  34. data/lib/grape_oauth2/mixins/sequel/client.rb +46 -0
  35. data/lib/grape_oauth2/responses/authorization.rb +10 -0
  36. data/lib/grape_oauth2/responses/base.rb +56 -0
  37. data/lib/grape_oauth2/responses/token.rb +10 -0
  38. data/lib/grape_oauth2/scopes.rb +74 -0
  39. data/lib/grape_oauth2/strategies/authorization_code.rb +38 -0
  40. data/lib/grape_oauth2/strategies/base.rb +47 -0
  41. data/lib/grape_oauth2/strategies/client_credentials.rb +20 -0
  42. data/lib/grape_oauth2/strategies/password.rb +22 -0
  43. data/lib/grape_oauth2/strategies/refresh_token.rb +47 -0
  44. data/lib/grape_oauth2/unique_token.rb +20 -0
  45. data/lib/grape_oauth2/version.rb +14 -0
  46. data/spec/configuration/config_spec.rb +231 -0
  47. data/spec/configuration/version_spec.rb +12 -0
  48. data/spec/dummy/endpoints/custom_authorization.rb +25 -0
  49. data/spec/dummy/endpoints/custom_token.rb +35 -0
  50. data/spec/dummy/endpoints/status.rb +25 -0
  51. data/spec/dummy/grape_oauth2_config.rb +11 -0
  52. data/spec/dummy/orm/active_record/app/config/db.rb +7 -0
  53. data/spec/dummy/orm/active_record/app/models/access_code.rb +3 -0
  54. data/spec/dummy/orm/active_record/app/models/access_token.rb +3 -0
  55. data/spec/dummy/orm/active_record/app/models/application.rb +3 -0
  56. data/spec/dummy/orm/active_record/app/models/application_record.rb +3 -0
  57. data/spec/dummy/orm/active_record/app/models/user.rb +10 -0
  58. data/spec/dummy/orm/active_record/app/twitter.rb +36 -0
  59. data/spec/dummy/orm/active_record/config.ru +7 -0
  60. data/spec/dummy/orm/active_record/db/schema.rb +53 -0
  61. data/spec/dummy/orm/mongoid/app/config/db.rb +6 -0
  62. data/spec/dummy/orm/mongoid/app/config/mongoid.yml +21 -0
  63. data/spec/dummy/orm/mongoid/app/models/access_code.rb +3 -0
  64. data/spec/dummy/orm/mongoid/app/models/access_token.rb +3 -0
  65. data/spec/dummy/orm/mongoid/app/models/application.rb +3 -0
  66. data/spec/dummy/orm/mongoid/app/models/user.rb +11 -0
  67. data/spec/dummy/orm/mongoid/app/twitter.rb +34 -0
  68. data/spec/dummy/orm/mongoid/config.ru +5 -0
  69. data/spec/dummy/orm/sequel/app/config/db.rb +1 -0
  70. data/spec/dummy/orm/sequel/app/models/access_code.rb +4 -0
  71. data/spec/dummy/orm/sequel/app/models/access_token.rb +4 -0
  72. data/spec/dummy/orm/sequel/app/models/application.rb +4 -0
  73. data/spec/dummy/orm/sequel/app/models/application_record.rb +2 -0
  74. data/spec/dummy/orm/sequel/app/models/user.rb +11 -0
  75. data/spec/dummy/orm/sequel/app/twitter.rb +47 -0
  76. data/spec/dummy/orm/sequel/config.ru +5 -0
  77. data/spec/dummy/orm/sequel/db/schema.rb +50 -0
  78. data/spec/lib/scopes_spec.rb +50 -0
  79. data/spec/mixins/active_record/access_token_spec.rb +185 -0
  80. data/spec/mixins/active_record/client_spec.rb +95 -0
  81. data/spec/mixins/mongoid/access_token_spec.rb +185 -0
  82. data/spec/mixins/mongoid/client_spec.rb +95 -0
  83. data/spec/mixins/sequel/access_token_spec.rb +185 -0
  84. data/spec/mixins/sequel/client_spec.rb +96 -0
  85. data/spec/requests/flows/authorization_code_spec.rb +67 -0
  86. data/spec/requests/flows/client_credentials_spec.rb +101 -0
  87. data/spec/requests/flows/password_spec.rb +210 -0
  88. data/spec/requests/flows/refresh_token_spec.rb +222 -0
  89. data/spec/requests/flows/revoke_token_spec.rb +103 -0
  90. data/spec/requests/protected_resources_spec.rb +64 -0
  91. data/spec/spec_helper.rb +60 -0
  92. data/spec/support/api_helper.rb +11 -0
  93. metadata +257 -0
@@ -0,0 +1,34 @@
1
+ module Grape
2
+ module OAuth2
3
+ # Grape::OAuth2 endpoints namespace
4
+ module Endpoints
5
+ # OAuth2 Grape authorization endpoint.
6
+ class Authorize < ::Grape::API
7
+ helpers Grape::OAuth2::Helpers::OAuthParams
8
+
9
+ namespace :oauth do
10
+ desc 'OAuth 2.0 Authorization Endpoint'
11
+
12
+ params do
13
+ use :oauth_authorization_params
14
+ end
15
+
16
+ post :authorize do
17
+ response = Grape::OAuth2::Generators::Authorization.generate_for(env)
18
+
19
+ # Status
20
+ status response.status
21
+
22
+ # Headers
23
+ response.headers.each do |key, value|
24
+ header key, value
25
+ end
26
+
27
+ # Body
28
+ body response.body
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,72 @@
1
+ module Grape
2
+ module OAuth2
3
+ # Grape::OAuth2 endpoints namespace
4
+ module Endpoints
5
+ # OAuth2 Grape token endpoint.
6
+ class Token < ::Grape::API
7
+ helpers Grape::OAuth2::Helpers::OAuthParams
8
+
9
+ namespace :oauth do
10
+ # @see https://tools.ietf.org/html/rfc6749#section-3.2
11
+ #
12
+ desc 'OAuth 2.0 Token Endpoint'
13
+
14
+ params do
15
+ use :oauth_token_params
16
+ end
17
+
18
+ post :token do
19
+ token_response = Grape::OAuth2::Generators::Token.generate_for(env)
20
+
21
+ # Status
22
+ status token_response.status
23
+
24
+ # Headers
25
+ token_response.headers.each do |key, value|
26
+ header key, value
27
+ end
28
+
29
+ # Body
30
+ body token_response.body
31
+ end
32
+
33
+ desc 'OAuth 2.0 Token Revocation'
34
+
35
+ params do
36
+ use :oauth_token_revocation_params
37
+ end
38
+
39
+ post :revoke do
40
+ access_token = Grape::OAuth2.config.access_token_class.authenticate(params[:token],
41
+ type: params[:token_type_hint])
42
+
43
+ if access_token
44
+ if access_token.client
45
+ request = Rack::OAuth2::Server::Token::Request.new(env)
46
+
47
+ # The authorization server, if applicable, first authenticates the client
48
+ # and checks its ownership of the provided token.
49
+ client = Grape::OAuth2::Strategies::Base.authenticate_client(request)
50
+ request.invalid_client! if client.nil?
51
+
52
+ access_token.revoke! if client && client == access_token.client
53
+ else
54
+ # Access token is public
55
+ access_token.revoke!
56
+ end
57
+ end
58
+
59
+ # The authorization server responds with HTTP status code 200 if the token
60
+ # has been revoked successfully or if the client submitted an invalid
61
+ # token.
62
+ #
63
+ # @see https://tools.ietf.org/html/rfc7009#section-2.2 Revocation Response
64
+ #
65
+ status 200
66
+ {}
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,24 @@
1
+ module Grape
2
+ module OAuth2
3
+ # Grape::OAuth2 version.
4
+ # @return [Gem::Version] version of the gem
5
+ #
6
+ def self.gem_version
7
+ Gem::Version.new VERSION::STRING
8
+ end
9
+
10
+ # Grape::OAuth2 semantic versioning module.
11
+ # Contains detailed info about gem version.
12
+ module VERSION
13
+ # Major version of the gem
14
+ MAJOR = 0
15
+ # Minor version of the gem
16
+ MINOR = 1
17
+ # Tiny version of the gem
18
+ TINY = 1
19
+
20
+ # Full gem version string
21
+ STRING = [MAJOR, MINOR, TINY].compact.join('.')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ module Grape
2
+ module OAuth2
3
+ module Generators
4
+ # OAuth2 Authorization generator class.
5
+ # Processes the request and builds the response.
6
+ class Authorization < Base
7
+ class << self
8
+ # Generates Authorization Response based on the request.
9
+ #
10
+ # @return [Grape::OAuth2::Responses::Authorization] response
11
+ #
12
+ def generate_for(env, &_block)
13
+ authorization = Rack::OAuth2::Server::Authorize.new do |request, response|
14
+ if block_given?
15
+ yield request, response
16
+ else
17
+ execute_default(request, response)
18
+ end
19
+ end
20
+
21
+ Grape::OAuth2::Responses::Authorization.new(authorization.call(env))
22
+ rescue Rack::OAuth2::Server::Authorize::BadRequest => error
23
+ error_response(error)
24
+ end
25
+
26
+ private
27
+
28
+ def error_response(error)
29
+ response = Rack::Response.new
30
+ response.status = error.status
31
+ response.header['Content-Type'] = 'application/json'
32
+ response.write(JSON.dump(Rack::OAuth2::Util.compact_hash(error.protocol_params)))
33
+
34
+ Grape::OAuth2::Responses::Authorization.new(response.finish)
35
+ end
36
+
37
+ def execute_default(request, response)
38
+ Grape::OAuth2::Strategies::AuthorizationCode.process(request, response)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,26 @@
1
+ module Grape
2
+ module OAuth2
3
+ module Generators
4
+ # Base class for Grape::OAuth2 generators.
5
+ # Grape::OAuth2 generators processes the requests and
6
+ # generates responses with Access Token or Authorization Code.
7
+ class Base
8
+ class << self
9
+ # Allowed grant types from the Grape::OAuth2 configuration.
10
+ #
11
+ # @return [Array]
12
+ # allowed grant types
13
+ #
14
+ def allowed_grants
15
+ config.allowed_grant_types
16
+ end
17
+
18
+ # Short getter for Grape::OAuth2 configuration.
19
+ def config
20
+ Grape::OAuth2.config
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module Grape
2
+ module OAuth2
3
+ module Generators
4
+ # OAuth2 Token generator class.
5
+ # Processes the request by required Grant Type and builds the response.
6
+ class Token < Base
7
+ # Grant type => OAuth2 strategy class
8
+ STRATEGY_CLASSES = {
9
+ password: Grape::OAuth2::Strategies::Password,
10
+ client_credentials: Grape::OAuth2::Strategies::ClientCredentials,
11
+ refresh_token: Grape::OAuth2::Strategies::RefreshToken
12
+ }.freeze
13
+
14
+ class << self
15
+ # Generates Token Response based on the request.
16
+ #
17
+ # @return [Grape::OAuth2::Responses::Token] response
18
+ #
19
+ def generate_for(env, &_block)
20
+ token = Rack::OAuth2::Server::Token.new do |request, response|
21
+ request.unsupported_grant_type! unless allowed_grants.include?(request.grant_type.to_s)
22
+
23
+ if block_given?
24
+ yield request, response
25
+ else
26
+ execute_default(request, response)
27
+ end
28
+ end
29
+
30
+ Grape::OAuth2::Responses::Token.new(token.call(env))
31
+ end
32
+
33
+ protected
34
+
35
+ # Runs default Grape::OAuth2 functionality for Token endpoint.
36
+ # In common it authenticates client (or/and any other objects) and
37
+ # grants the Access Token or Auth Code.
38
+ #
39
+ # @param request [Rack::Request] request object
40
+ # @param response [Rack::Response] response object
41
+ #
42
+ def execute_default(request, response)
43
+ strategy = find_strategy(request.grant_type) || request.invalid_grant!
44
+ response.access_token = strategy.process(request)
45
+ end
46
+
47
+ # Returns Grape::OAuth2 strategy class by Grant Type.
48
+ #
49
+ # @param grant_type [Symbol]
50
+ # grant type value
51
+ #
52
+ # @return [Password, ClientCredentials, RefreshToken]
53
+ # strategy class
54
+ #
55
+ def find_strategy(grant_type)
56
+ STRATEGY_CLASSES[grant_type]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module Grape
2
+ module OAuth2
3
+ module Helpers
4
+ # Set of Grape OAuth2 helpers.
5
+ module AccessTokenHelpers
6
+ extend ::Grape::API::Helpers
7
+
8
+ # Adds OAuth2 Access Token protection for Grape routes.
9
+ #
10
+ # @param scopes [Array]
11
+ # set of scopes required to access the endpoint
12
+ #
13
+ # @raise [Rack::OAuth2::Server::Resource::Bearer::Unauthorized]
14
+ # invalid Access Token value
15
+ # @raise [Rack::OAuth2::Server::Resource::Bearer::Forbidden]
16
+ # Access Token expired, revoked or does't have required scopes
17
+ #
18
+ def access_token_required!(*scopes)
19
+ endpoint_scopes = env['api.endpoint'].options[:route_options][:scopes]
20
+ required_scopes = endpoint_scopes.presence || scopes
21
+
22
+ raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized if current_access_token.nil?
23
+ raise Rack::OAuth2::Server::Resource::Bearer::Forbidden unless valid_access_token?(required_scopes)
24
+ end
25
+
26
+ # Returns Resource Owner from the Access Token
27
+ # found by access_token value passed with the request.
28
+ def current_resource_owner
29
+ @_current_resource_owner ||= current_access_token.resource_owner
30
+ end
31
+
32
+ # Returns Access Token instance found by
33
+ # access_token value passed with the request.
34
+ def current_access_token
35
+ @_current_access_token ||= request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
36
+ end
37
+
38
+ private
39
+
40
+ # Validate current access token not to be expired or revoked
41
+ # and has all the requested scopes.
42
+ #
43
+ # @return [Boolean]
44
+ # true if current Access Token not expired, not revoked and scopes match
45
+ # false in other cases.
46
+ #
47
+ def valid_access_token?(scopes)
48
+ !current_access_token.revoked? && !current_access_token.expired? &&
49
+ Grape::OAuth2.config.scopes_validator.new(scopes).valid_for?(current_access_token)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ module Grape
2
+ module OAuth2
3
+ module Helpers
4
+ # Grape Helper object for OAuth2 requests params.
5
+ # Used fin default Grape::OAuth2 gem endpoints and can be used
6
+ # for custom one.
7
+ module OAuthParams
8
+ extend ::Grape::API::Helpers
9
+
10
+ # Params are optional in order to process them correctly in accordance
11
+ # with the RFC 6749 (invalid_client, unsupported_grant_type, etc.)
12
+ params :oauth_token_params do
13
+ optional :grant_type, type: String, desc: 'Grant type'
14
+ optional :client_id, type: String, desc: 'Client ID'
15
+ optional :client_secret, type: String, desc: 'Client secret'
16
+ optional :refresh_token, type: String, desc: 'Refresh Token'
17
+ end
18
+
19
+ # Params for authorization request.
20
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.1.1 Authorization Request
21
+ params :oauth_authorization_params do
22
+ optional :response_type, type: String, desc: 'Response type'
23
+ optional :client_id, type: String, desc: 'Client ID'
24
+ optional :redirect_uri, type: String, desc: 'Redirect URI'
25
+ optional :scope, type: String, desc: 'Authorization scopes'
26
+ optional :state, type: String, desc: 'State'
27
+ end
28
+
29
+ # Params for token revocation.
30
+ # @see https://tools.ietf.org/html/rfc7009#section-2.1 OAuth 2.0 Token Revocation
31
+ params :oauth_token_revocation_params do
32
+ requires :token, type: String, desc: 'The token that the client wants to get revoked'
33
+ optional :token_type_hint, type: String,
34
+ values: %w(access_token refresh_token),
35
+ default: 'access_token',
36
+ desc: 'A hint about the type of the token submitted for revocation'
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ module Grape
2
+ module OAuth2
3
+ module ActiveRecord
4
+ # Grape::OAuth2 Authorization Grant role mixin for ActiveRecord.
5
+ # Includes all the required API, associations, validations and callbacks.
6
+ module AccessGrant
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ belongs_to :client, class_name: Grape::OAuth2.config.client_class_name,
11
+ foreign_key: :client_id
12
+
13
+ belongs_to :resource_owner, class_name: Grape::OAuth2.config.resource_owner_class_name,
14
+ foreign_key: :resource_owner_id
15
+
16
+ # resource_owner_id - required!
17
+ validates :client_id, :redirect_uri, presence: true
18
+ validates :token, presence: true, uniqueness: true
19
+
20
+ before_validation :generate_token, on: :create
21
+ before_validation :setup_expiration, on: :create
22
+
23
+ class << self
24
+ def create_for(client, resource_owner, redirect_uri, scopes = nil)
25
+ create(
26
+ client_id: client.id,
27
+ resource_owner_id: resource_owner && resource_owner.id,
28
+ redirect_uri: redirect_uri,
29
+ scopes: scopes.to_s
30
+ )
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def generate_token
37
+ self.token = Grape::OAuth2.config.token_generator.generate(attributes)
38
+ end
39
+
40
+ def setup_expiration
41
+ self.expires_at = Time.now.utc + Grape::OAuth2.config.authorization_code_lifetime if expires_at.nil?
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,75 @@
1
+ module Grape
2
+ module OAuth2
3
+ module ActiveRecord
4
+ # Grape::OAuth2 Access Token role mixin for ActiveRecord.
5
+ # Includes all the required API, associations, validations and callbacks.
6
+ module AccessToken
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ belongs_to :client, class_name: Grape::OAuth2.config.client_class_name,
11
+ foreign_key: :client_id
12
+
13
+ belongs_to :resource_owner, class_name: Grape::OAuth2.config.resource_owner_class_name,
14
+ foreign_key: :resource_owner_id
15
+
16
+ validates :token, presence: true, uniqueness: true
17
+
18
+ before_validation :setup_expiration, on: :create
19
+ before_validation :generate_tokens, on: :create
20
+
21
+ class << self
22
+ def create_for(client, resource_owner, scopes = nil)
23
+ create(
24
+ client: client,
25
+ resource_owner: resource_owner,
26
+ scopes: scopes.to_s
27
+ )
28
+ end
29
+
30
+ def authenticate(token, type: :access_token)
31
+ if type && type.to_sym == :refresh_token
32
+ find_by(refresh_token: token.to_s)
33
+ else
34
+ find_by(token: token.to_s)
35
+ end
36
+ end
37
+ end
38
+
39
+ def expired?
40
+ !expires_at.nil? && Time.now.utc > expires_at
41
+ end
42
+
43
+ def revoked?
44
+ !revoked_at.nil? && revoked_at <= Time.now.utc
45
+ end
46
+
47
+ def revoke!(revoked_at = Time.now)
48
+ update_column :revoked_at, revoked_at.utc
49
+ end
50
+
51
+ def to_bearer_token
52
+ {
53
+ access_token: token,
54
+ expires_in: expires_at && Grape::OAuth2.config.access_token_lifetime.to_i,
55
+ refresh_token: refresh_token,
56
+ scope: scopes
57
+ }
58
+ end
59
+
60
+ protected
61
+
62
+ def generate_tokens
63
+ self.token = Grape::OAuth2.config.token_generator.generate(attributes) if token.blank?
64
+ self.refresh_token = Grape::OAuth2::UniqueToken.generate if Grape::OAuth2.config.issue_refresh_token
65
+ end
66
+
67
+ def setup_expiration
68
+ expires_in = Grape::OAuth2.config.access_token_lifetime
69
+ self.expires_at = Time.now + expires_in if expires_at.nil? && !expires_in.nil?
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end