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
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/setup'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Default: run specs.'
5
+ task default: :spec
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |config|
8
+ config.verbose = false
9
+ end
10
+
11
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,25 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ platforms :jruby do
6
+ gem 'jdbc-sqlite3'
7
+ end
8
+
9
+ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
10
+ gem 'sqlite3'
11
+ end
12
+
13
+ gem 'otr-activerecord'
14
+
15
+ gem 'activerecord'
16
+ gem 'bcrypt'
17
+
18
+ group :test do
19
+ gem 'rspec-rails', '~> 3.5'
20
+ gem 'database_cleaner'
21
+ gem 'rack-test', require: 'rack/test'
22
+ gem 'coveralls', require: false
23
+ end
24
+
25
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'mongoid', '~> 6'
6
+
7
+ group :test do
8
+ gem 'rspec-rails', '~> 3.4'
9
+ gem 'database_cleaner'
10
+ gem 'rack-test', require: 'rack/test'
11
+ gem 'coveralls', require: false
12
+ end
13
+
14
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
@@ -0,0 +1,24 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ platforms :jruby do
6
+ gem 'jdbc-sqlite3'
7
+ end
8
+
9
+ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
10
+ gem 'sqlite3'
11
+ end
12
+
13
+ gem 'bcrypt'
14
+ gem 'sequel'
15
+ gem 'sequel_secure_password'
16
+
17
+ group :test do
18
+ gem 'rspec-rails', '~> 3.4'
19
+ gem 'database_cleaner'
20
+ gem 'rack-test', require: 'rack/test'
21
+ gem 'coveralls', require: false
22
+ end
23
+
24
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'grape_oauth2/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'grape_oauth2'
7
+ gem.version = Grape::OAuth2.gem_version
8
+ gem.authors = ['Nikita Bulai']
9
+ gem.date = '2016-05-31'
10
+ gem.email = ['bulajnikita@gmail.com']
11
+ gem.homepage = 'http://github.com/nbulaj/grape-oauth2'
12
+ gem.summary = 'Grape OAuth2 provider'
13
+ gem.description = 'Provides flexible, ORM-agnostic, fully customizable and simple OAuth2 support for Grape APIs'
14
+ gem.license = 'MIT'
15
+
16
+ gem.require_paths = %w(lib)
17
+ gem.files = `git ls-files`.split($RS)
18
+ gem.test_files = Dir['spec/**/*']
19
+
20
+ gem.required_ruby_version = '>= 2.2.2'
21
+
22
+ gem.add_runtime_dependency 'grape', '~> 0.16'
23
+ gem.add_runtime_dependency 'rack-oauth2', '~> 1.3.0', '>= 1.3.0'
24
+
25
+ gem.add_development_dependency 'rspec-rails', '~> 3.4.0', '>= 3.4.0'
26
+ gem.add_development_dependency 'database_cleaner', '~> 1.5.0', '>= 1.5.0'
27
+ end
data/grape_oauth2.png ADDED
Binary file
@@ -0,0 +1,129 @@
1
+ require 'grape'
2
+ require 'rack/oauth2'
3
+
4
+ require 'grape_oauth2/version'
5
+ require 'grape_oauth2/configuration/validation'
6
+ require 'grape_oauth2/configuration/class_accessors'
7
+ require 'grape_oauth2/configuration'
8
+ require 'grape_oauth2/scopes'
9
+ require 'grape_oauth2/unique_token'
10
+
11
+ # NOTE: Extract to separate gems!!!
12
+ # This gem should contains only the core functionality and all mixins
13
+ # need to be moved to their own repos with their own tests.
14
+
15
+ # Mixins
16
+ if defined?(ActiveRecord::Base)
17
+ require 'grape_oauth2/mixins/active_record/access_token'
18
+ require 'grape_oauth2/mixins/active_record/access_grant'
19
+ require 'grape_oauth2/mixins/active_record/client'
20
+ end
21
+
22
+ if defined?(Sequel::Model)
23
+ require 'grape_oauth2/mixins/sequel/access_token'
24
+ require 'grape_oauth2/mixins/sequel/access_grant'
25
+ require 'grape_oauth2/mixins/sequel/client'
26
+ end
27
+
28
+ if defined?(Mongoid::Document)
29
+ require 'grape_oauth2/mixins/mongoid/access_token'
30
+ require 'grape_oauth2/mixins/mongoid/access_grant'
31
+ require 'grape_oauth2/mixins/mongoid/client'
32
+ end
33
+
34
+ # Authorization Grants aka Flows (Strategies)
35
+ require 'grape_oauth2/strategies/base'
36
+ require 'grape_oauth2/strategies/authorization_code'
37
+ require 'grape_oauth2/strategies/password'
38
+ require 'grape_oauth2/strategies/client_credentials'
39
+ require 'grape_oauth2/strategies/refresh_token'
40
+
41
+ # Generators
42
+ require 'grape_oauth2/generators/base'
43
+ require 'grape_oauth2/generators/token'
44
+ require 'grape_oauth2/generators/authorization'
45
+
46
+ # Grape Helpers
47
+ require 'grape_oauth2/helpers/access_token_helpers'
48
+ require 'grape_oauth2/helpers/oauth_params'
49
+
50
+ # Responses
51
+ require 'grape_oauth2/responses/base'
52
+ require 'grape_oauth2/responses/authorization'
53
+ require 'grape_oauth2/responses/token'
54
+
55
+ # Grape Endpoints
56
+ require 'grape_oauth2/endpoints/token'
57
+ require 'grape_oauth2/endpoints/authorize'
58
+
59
+ # Use Grape namespace for the gem.
60
+ module Grape
61
+ # Main Grape::OAuth2 module.
62
+ module OAuth2
63
+ class << self
64
+ # Grape::OAuth2 configuration.
65
+ #
66
+ # @return [Grape::OAuth2::Configuration]
67
+ # configuration object
68
+ #
69
+ def config
70
+ @config ||= Grape::OAuth2::Configuration.new
71
+ end
72
+
73
+ # Configures Grape::OAuth2.
74
+ # Yields Grape::OAuth2::Configuration instance to the block.
75
+ def configure
76
+ yield config
77
+ end
78
+
79
+ # Validates Grape::OAuth2 configuration to be set correctly.
80
+ def check_configuration!
81
+ config.check!
82
+ end
83
+
84
+ # Grape::OAuth2 default middleware.
85
+ def middleware
86
+ [Rack::OAuth2::Server::Resource::Bearer, config.realm, config.token_authenticator]
87
+ end
88
+
89
+ # Method for injecting Grape::OAuth2 endpoints and helpers
90
+ # into Grape API class. Automatically set required middleware,
91
+ # OAuth2 helpers and mounts all (or configured) endpoints.
92
+ #
93
+ # @param endpoints [Array<Symbol>, Array<String>] endpoints to add
94
+ #
95
+ def api(*endpoints)
96
+ inject_to_api do |api|
97
+ api.use(*Grape::OAuth2.middleware)
98
+ api.helpers(Grape::OAuth2::Helpers::AccessTokenHelpers)
99
+
100
+ (endpoints.presence || endpoints_mapping.keys).each do |name|
101
+ endpoint = endpoints_mapping[name.to_sym]
102
+ raise ArgumentError, "Unrecognized endpoint: #{endpoint}" if endpoint.nil?
103
+
104
+ api.mount(endpoint)
105
+ end
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def endpoints_mapping
112
+ {
113
+ token: ::Grape::OAuth2::Endpoints::Token,
114
+ authorize: ::Grape::OAuth2::Endpoints::Authorize
115
+ }
116
+ end
117
+
118
+ def inject_to_api(&_block)
119
+ raise ArgumentError, 'block must be specified!' unless block_given?
120
+
121
+ Module.new do |mod|
122
+ mod.define_singleton_method :included do |base|
123
+ yield base
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,143 @@
1
+ module Grape
2
+ module OAuth2
3
+ # Grape::OAuth2 configuration class.
4
+ # Contains default or customized options that would be used
5
+ # in OAuth2 endpoints and helpers.
6
+ class Configuration
7
+ # Default Grape::OAuth2 configuration error class.
8
+ Error = Class.new(StandardError)
9
+ # Grape::OAuth2 configuration error for missing API required for OAuth2 classes.
10
+ APIMissing = Class.new(Error)
11
+
12
+ include Validation
13
+ include ClassAccessors
14
+
15
+ # Default Access Token TTL (in seconds)
16
+ DEFAULT_TOKEN_LIFETIME = 7200
17
+ # Default Authorization Code TTL ()in seconds)
18
+ DEFAULT_CODE_LIFETIME = 1800
19
+
20
+ # Default realm value
21
+ DEFAULT_REALM = 'OAuth 2.0'.freeze
22
+
23
+ # Currently supported (be the gem) OAuth2 grant types
24
+ SUPPORTED_GRANT_TYPES = %w(password client_credentials refresh_token).freeze
25
+
26
+ # The names of the classes that represents OAuth2 roles
27
+ #
28
+ # @return [String] class name
29
+ #
30
+ attr_accessor :access_token_class_name, :access_grant_class_name,
31
+ :client_class_name, :resource_owner_class_name
32
+
33
+ # Class name for the OAuth2 helper class that validates requested scopes against Access Token scopes
34
+ #
35
+ # @return [String] scopes validator class name
36
+ #
37
+ attr_accessor :scopes_validator_class_name
38
+
39
+ # Class name for the OAuth2 helper class that generates unique token values
40
+ #
41
+ # @return [String] token generator class name
42
+ #
43
+ attr_accessor :token_generator_class_name
44
+
45
+ # OAuth2 grant types (flows) allowed to be processed
46
+ #
47
+ # @return [Array<String>] grant types
48
+ #
49
+ attr_accessor :allowed_grant_types
50
+
51
+ # Access Token and Authorization Code lifetime in seconds
52
+ attr_accessor :authorization_code_lifetime, :access_token_lifetime
53
+
54
+ # Specifies whether to generate a Refresh Token when creating an Access Token
55
+ #
56
+ # @return [Boolean] true if need to generate refresh token, false in other case
57
+ #
58
+ attr_accessor :issue_refresh_token
59
+
60
+ # Realm value
61
+ #
62
+ # @return [String] realm
63
+ #
64
+ attr_accessor :realm
65
+
66
+ # Access Token authenticator block option for customization
67
+ attr_accessor :token_authenticator
68
+
69
+ # Callback that would be invoked during processing of Refresh Token request for
70
+ # the original Access Token found by token value
71
+ attr_accessor :on_refresh
72
+
73
+ def initialize
74
+ reset!
75
+ end
76
+
77
+ # Default Access Token authenticator block.
78
+ # Validates token value passed with the request params.
79
+ def default_token_authenticator
80
+ lambda do |request|
81
+ access_token_class.authenticate(request.access_token) || request.invalid_token!
82
+ end
83
+ end
84
+
85
+ # Accessor for Access Token authenticator block. Set it to proc
86
+ # if called with block or returns current value of the accessor.
87
+ def token_authenticator(&block)
88
+ if block_given?
89
+ instance_variable_set(:'@token_authenticator', block)
90
+ else
91
+ instance_variable_get(:'@token_authenticator')
92
+ end
93
+ end
94
+
95
+ # Accessor for on_refresh callback. Set callback proc
96
+ # if called with block or returns current value of the accessor.
97
+ def on_refresh(&block)
98
+ if block_given?
99
+ instance_variable_set(:'@on_refresh', block)
100
+ else
101
+ instance_variable_get(:'@on_refresh')
102
+ end
103
+ end
104
+
105
+ # Indicates if on_refresh callback can be invoked.
106
+ #
107
+ # @return [Boolean]
108
+ # true if callback can be invoked and false in other cases
109
+ #
110
+ def on_refresh_runnable?
111
+ !on_refresh.nil? && on_refresh != :nothing
112
+ end
113
+
114
+ # Reset configuration to default options values.
115
+ def reset!
116
+ initialize_classes
117
+ initialize_authenticators
118
+
119
+ self.access_token_lifetime = DEFAULT_TOKEN_LIFETIME
120
+ self.authorization_code_lifetime = DEFAULT_CODE_LIFETIME
121
+ self.allowed_grant_types = %w(password client_credentials)
122
+
123
+ self.issue_refresh_token = false
124
+ self.on_refresh = :nothing
125
+
126
+ self.realm = DEFAULT_REALM
127
+ end
128
+
129
+ private
130
+
131
+ # Sets OAuth2 helpers classes to gem defaults.
132
+ def initialize_classes
133
+ self.scopes_validator_class_name = Grape::OAuth2::Scopes.name
134
+ self.token_generator_class_name = Grape::OAuth2::UniqueToken.name
135
+ end
136
+
137
+ # Sets authenticators to gem defaults.
138
+ def initialize_authenticators
139
+ self.token_authenticator = default_token_authenticator
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,36 @@
1
+ module Grape
2
+ module OAuth2
3
+ # Grape::OAuth2 accessors for configured classes.
4
+ module ClassAccessors
5
+ # Returns Access Token class by configured name
6
+ def access_token_class
7
+ @_access_token_class ||= access_token_class_name.constantize
8
+ end
9
+
10
+ # Returns Resource Owner class by configured name
11
+ def resource_owner_class
12
+ @_resource_owner_class ||= resource_owner_class_name.constantize
13
+ end
14
+
15
+ # Returns Client class by configured name
16
+ def client_class
17
+ @_client_class ||= client_class_name.constantize
18
+ end
19
+
20
+ # Returns Access Grant class by configured name
21
+ def access_grant_class
22
+ @_access_grant_class ||= access_grant_class_name.constantize
23
+ end
24
+
25
+ # Returns Scopes Validator class by configured name
26
+ def scopes_validator
27
+ scopes_validator_class_name.constantize
28
+ end
29
+
30
+ # Returns Token Generator class by configured name
31
+ def token_generator
32
+ token_generator_class_name.constantize
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ module Grape
2
+ module OAuth2
3
+ class Configuration
4
+ # Validates Grape::OAuth2 configuration.
5
+ module Validation
6
+ # Checks configuration to be set correctly
7
+ # (required classes must be defined and implement specific set of API).
8
+ def check!
9
+ check_required_classes!
10
+ check_required_classes_api!
11
+ end
12
+
13
+ private
14
+
15
+ # API mapping.
16
+ # Classes, that represents OAuth2 roles, must have described methods.
17
+ REQUIRED_CLASSES_API = {
18
+ access_token_class: {
19
+ class_methods: %i(authenticate create_for),
20
+ instance_methods: %i(expired? revoked? revoke! to_bearer_token)
21
+ },
22
+ client_class: {
23
+ class_methods: %i(authenticate)
24
+ },
25
+ token_generator: {
26
+ class_methods: %i(generate)
27
+ },
28
+ scopes_validator: {
29
+ instance_methods: %i(valid_for?)
30
+ }
31
+ }.freeze
32
+
33
+ # Validates that required classes defined.
34
+ def check_required_classes!
35
+ REQUIRED_CLASSES_API.keys.each do |klass|
36
+ begin
37
+ object = send(klass)
38
+ rescue NoMethodError
39
+ raise Error, "'#{klass}' must be defined!" if object.nil? || !defined?(object)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Validates that required classes have all the API.
45
+ def check_required_classes_api!
46
+ REQUIRED_CLASSES_API.each do |klass, api_methods|
47
+ check_class_methods(klass, api_methods[:class_methods])
48
+ check_instance_methods(klass, api_methods[:instance_methods])
49
+ end
50
+ end
51
+
52
+ # Validates that required classes have required class methods.
53
+ def check_class_methods(klass, required_methods)
54
+ (required_methods || []).each do |method|
55
+ method_exist = send(klass).respond_to?(method)
56
+ raise APIMissing, "Class method '#{method}' must be defined for the '#{klass}'!" unless method_exist
57
+ end
58
+ end
59
+
60
+ # Validates that required classes have required instance methods.
61
+ def check_instance_methods(klass, required_methods)
62
+ (required_methods || []).each do |method|
63
+ unless send(klass).method_defined?(method)
64
+ raise APIMissing, "Instance method '#{method}' must be defined for the '#{klass}'!"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end