grape_oauth2 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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