doorkeeper-rethinkdb 1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +39 -0
- data/Rakefile +23 -0
- data/lib/doorkeeper-rethinkdb.rb +7 -0
- data/lib/doorkeeper-rethinkdb/version.rb +3 -0
- data/lib/support/orm/rethinkdb.rb +20 -0
- data/lib/support/orm/rethinkdb/access_grant.rb +122 -0
- data/lib/support/orm/rethinkdb/access_token.rb +376 -0
- data/lib/support/orm/rethinkdb/application.rb +93 -0
- data/lib/support/orm/rethinkdb/scopes.rb +22 -0
- data/lib/support/orm/rethinkdb/timestamps.rb +32 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 856cb49c0238f249ed8a203b4dfb93bf57b1b2edf2953d47c8d0b1708a74063e
|
4
|
+
data.tar.gz: 8b5171dbe6d20e837f8a7b238079dd26b07808db5adc5192cab2439d597449e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a1f0cc85cd8764b277cf4872e9284044305552a4f11f150f537489317689a0523864a917de070e5482f6d83179256c28e37026284a29ac8d6b4a9b76dd138aa
|
7
|
+
data.tar.gz: 26b37570029b3087f4a179c5ced78085fb4e952a30aed77830f9143f6a64526c06490d8b701be7b1575bbbce90c487d7a460e922a6b8d3899d7745d451a1343c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 ACAProjects
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# doorkeeper-rethinkdb extension
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
doorkeeper-rethinkdb provides doorkeeper support to rethinkdb
|
6
|
+
To start using it, add to your Gemfile:
|
7
|
+
|
8
|
+
``` ruby
|
9
|
+
gem "doorkeeper-rethinkdb"
|
10
|
+
```
|
11
|
+
|
12
|
+
Run [doorkeeper’s installation generator]:
|
13
|
+
|
14
|
+
rails generate doorkeeper:install
|
15
|
+
|
16
|
+
[doorkeeper’s installation generator]: https://github.com/doorkeeper-gem/doorkeeper#installation
|
17
|
+
|
18
|
+
This will install the doorkeeper initializer into
|
19
|
+
`config/initializers/doorkeeper.rb`.
|
20
|
+
|
21
|
+
Set the ORM configuration:
|
22
|
+
|
23
|
+
``` ruby
|
24
|
+
Doorkeeper.configure do
|
25
|
+
orm :rethinkdb
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
## Tests
|
30
|
+
|
31
|
+
To run tests, clone this repository and run `rake`. It will copy and run
|
32
|
+
doorkeeper’s original test suite, after configuring the ORM according to the
|
33
|
+
variables defined in `.travis.yml` file.
|
34
|
+
|
35
|
+
|
36
|
+
---
|
37
|
+
|
38
|
+
Please refer to https://github.com/doorkeeper-gem/doorkeeper for instructions on
|
39
|
+
doorkeeper’s project.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
task :load_doorkeeper do
|
5
|
+
`rm -rf spec/`
|
6
|
+
`git checkout -- spec`
|
7
|
+
`git submodule init`
|
8
|
+
`git submodule update`
|
9
|
+
`cp -r -n doorkeeper/spec .`
|
10
|
+
`bundle exec rspec`
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Default: run specs.'
|
14
|
+
task default: :spec
|
15
|
+
|
16
|
+
desc 'Clone down doorkeeper specs'
|
17
|
+
task spec: :load_doorkeeper
|
18
|
+
|
19
|
+
RSpec::Core::RakeTask.new(:spec) do |config|
|
20
|
+
config.verbose = false
|
21
|
+
end
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module Orm
|
3
|
+
module Rethinkdb
|
4
|
+
def self.initialize_models!
|
5
|
+
require 'support/orm/rethinkdb/timestamps'
|
6
|
+
require 'support/orm/rethinkdb/scopes'
|
7
|
+
require 'support/orm/rethinkdb/access_grant'
|
8
|
+
require 'support/orm/rethinkdb/access_token'
|
9
|
+
require 'support/orm/rethinkdb/application'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.initialize_application_owner!
|
13
|
+
#require 'doorkeeper/models/concerns/ownership'
|
14
|
+
#Doorkeeper::Application.send :include, Doorkeeper::Models::Ownership
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.check_requirements!(_config); end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
module Doorkeeper
|
3
|
+
class AccessGrant
|
4
|
+
include NoBrainer::Document
|
5
|
+
|
6
|
+
table_config name: 'doorkeeper_grant'
|
7
|
+
|
8
|
+
include OAuth::Helpers
|
9
|
+
include Models::Expirable
|
10
|
+
include Models::Revocable
|
11
|
+
include Models::Accessible
|
12
|
+
include Models::Scopes
|
13
|
+
include Models::SecretStorable
|
14
|
+
|
15
|
+
include Timestamps
|
16
|
+
|
17
|
+
belongs_to :application, class_name: 'Doorkeeper::Application'
|
18
|
+
|
19
|
+
field :resource_owner_id, type: String
|
20
|
+
field :token, type: String, uniq: true, index: true
|
21
|
+
field :scopes, type: String
|
22
|
+
field :redirect_uri, type: String
|
23
|
+
|
24
|
+
field :expires_in, type: Integer
|
25
|
+
field :ttl, type: Integer
|
26
|
+
field :revoked_at, type: Time
|
27
|
+
|
28
|
+
# PKCE support
|
29
|
+
field :code_challenge, type: String
|
30
|
+
field :code_challenge_method, type: String
|
31
|
+
|
32
|
+
index :ttl
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def by_token(token)
|
36
|
+
find_by_plaintext_token(:token, token)
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_by_plaintext_token(attr, token)
|
40
|
+
# We are not implementing the fallback strategy
|
41
|
+
where(attr => secret_strategy.transform_secret(token.to_s)).first
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
|
45
|
+
#
|
46
|
+
# @return [#to_s] An encoded code challenge based on the provided verifier
|
47
|
+
# suitable for PKCE validation
|
48
|
+
#
|
49
|
+
def generate_code_challenge(code_verifier)
|
50
|
+
padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
|
51
|
+
padded_result.split("=")[0] # Remove any trailing '='
|
52
|
+
end
|
53
|
+
|
54
|
+
def pkce_supported?
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Determines the secret storing transformer
|
60
|
+
# Unless configured otherwise, uses the plain secret strategy
|
61
|
+
#
|
62
|
+
# @return [Doorkeeper::SecretStoring::Base]
|
63
|
+
#
|
64
|
+
def secret_strategy
|
65
|
+
::Doorkeeper.config.token_secret_strategy
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Determine the fallback storing strategy
|
70
|
+
# Unless configured, there will be no fallback
|
71
|
+
#
|
72
|
+
# @return [Doorkeeper::SecretStoring::Base]
|
73
|
+
#
|
74
|
+
def fallback_secret_strategy
|
75
|
+
::Doorkeeper.config.token_secret_fallback_strategy
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
validates :resource_owner_id, :application, :token, :expires_in, :redirect_uri, presence: true
|
80
|
+
before_validation :generate_token, on: :create
|
81
|
+
|
82
|
+
def transaction; yield; end
|
83
|
+
def lock!; end
|
84
|
+
|
85
|
+
def uses_pkce?
|
86
|
+
self.code_challenge.present?
|
87
|
+
end
|
88
|
+
|
89
|
+
# We keep a volatile copy of the raw token for initial communication
|
90
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
91
|
+
#
|
92
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric encryption)
|
93
|
+
# while hashing strategies do not, so you cannot rely on this value
|
94
|
+
# returning a present value for persisted tokens.
|
95
|
+
def plaintext_token
|
96
|
+
if secret_strategy.allows_restoring_secrets?
|
97
|
+
secret_strategy.restore_secret(self, :token)
|
98
|
+
else
|
99
|
+
@raw_token
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def revoke(clock = Time)
|
104
|
+
self.revoked_at = clock.now.utc
|
105
|
+
self.save!
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Generates token value with UniqueToken class.
|
111
|
+
#
|
112
|
+
# @return [String] token value
|
113
|
+
#
|
114
|
+
def generate_token
|
115
|
+
self.ttl = (self.created_at + self.expires_in + 30).to_i if self.created_at && self.expires_in
|
116
|
+
if self.token.blank?
|
117
|
+
@raw_token = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
118
|
+
secret_strategy.store_secret(self, :token, @raw_token)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,376 @@
|
|
1
|
+
|
2
|
+
module Doorkeeper
|
3
|
+
class AccessToken
|
4
|
+
include NoBrainer::Document
|
5
|
+
|
6
|
+
table_config name: 'doorkeeper_token'
|
7
|
+
|
8
|
+
include OAuth::Helpers
|
9
|
+
include Models::Expirable
|
10
|
+
include Models::Reusable
|
11
|
+
include Models::Revocable
|
12
|
+
include Models::Accessible
|
13
|
+
include Models::Scopes
|
14
|
+
include Models::SecretStorable
|
15
|
+
|
16
|
+
include ::Doorkeeper::Rethinkdb::Timestamps
|
17
|
+
|
18
|
+
attr_writer :use_refresh_token
|
19
|
+
|
20
|
+
belongs_to :application, class_name: 'Doorkeeper::Application'
|
21
|
+
|
22
|
+
field :resource_owner_id, type: String, index: true
|
23
|
+
field :token, type: String, uniq: true, index: true
|
24
|
+
field :refresh_token, type: String, index: true
|
25
|
+
|
26
|
+
field :scopes, type: String
|
27
|
+
field :previous_refresh_token, type: String, default: ->{ '' }
|
28
|
+
field :expires_in, type: Integer
|
29
|
+
field :ttl, type: Integer
|
30
|
+
field :revoked_at, type: Time
|
31
|
+
|
32
|
+
index :ttl
|
33
|
+
|
34
|
+
alias :plaintext_token :token
|
35
|
+
alias :plaintext_refresh_token :refresh_token
|
36
|
+
|
37
|
+
validates :resource_owner_id, :application, :expires_in, :token, presence: true
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def refresh_token_revoked_on_use?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_by_plaintext_token(attr, token)
|
45
|
+
# We are not implementing the fallback strategy
|
46
|
+
where(attr => secret_strategy.transform_secret(token.to_s)).first
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns an instance of the Doorkeeper::AccessToken with
|
50
|
+
# specific token value.
|
51
|
+
#
|
52
|
+
# @param token [#to_s]
|
53
|
+
# token value (any object that responds to `#to_s`)
|
54
|
+
#
|
55
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
56
|
+
# if there is no record with such token
|
57
|
+
#
|
58
|
+
def by_token(token)
|
59
|
+
find_by_plaintext_token(:token, token)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns an instance of the Doorkeeper::AccessToken
|
63
|
+
# with specific token value.
|
64
|
+
#
|
65
|
+
# @param refresh_token [#to_s]
|
66
|
+
# refresh token value (any object that responds to `#to_s`)
|
67
|
+
#
|
68
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
69
|
+
# if there is no record with such refresh token
|
70
|
+
#
|
71
|
+
def by_refresh_token(refresh_token)
|
72
|
+
find_by_plaintext_token(:refresh_token, refresh_token)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns an instance of the Doorkeeper::AccessToken
|
76
|
+
# found by previous refresh token. Keep in mind that value
|
77
|
+
# of the previous_refresh_token isn't encrypted using
|
78
|
+
# secrets strategy.
|
79
|
+
#
|
80
|
+
# @param previous_refresh_token [#to_s]
|
81
|
+
# previous refresh token value (any object that responds to `#to_s`)
|
82
|
+
#
|
83
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
84
|
+
# if there is no record with such refresh token
|
85
|
+
#
|
86
|
+
def by_previous_refresh_token(previous_refresh_token)
|
87
|
+
return nil unless previous_refresh_token.present?
|
88
|
+
where(previous_refresh_token: previous_refresh_token).first
|
89
|
+
end
|
90
|
+
|
91
|
+
# Revokes AccessToken records that have not been revoked and associated
|
92
|
+
# with the specific Application and Resource Owner.
|
93
|
+
#
|
94
|
+
# @param application_id [Integer]
|
95
|
+
# ID of the Application
|
96
|
+
# @param resource_owner [ActiveRecord::Base]
|
97
|
+
# instance of the Resource Owner model
|
98
|
+
#
|
99
|
+
def revoke_all_for(application_id, resource_owner)
|
100
|
+
where(application_id: application_id, resource_owner_id: resource_owner.id).each do |at|
|
101
|
+
at.revoke
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Looking for not revoked Access Token record that belongs to specific
|
106
|
+
# Application and Resource Owner.
|
107
|
+
#
|
108
|
+
# @param application_id [Integer]
|
109
|
+
# ID of the Application model instance
|
110
|
+
# @param resource_owner_id [Integer]
|
111
|
+
# ID of the Resource Owner model instance
|
112
|
+
#
|
113
|
+
# @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
|
114
|
+
# nil if nothing was found
|
115
|
+
#
|
116
|
+
def last_authorized_token_for(application_id, resource_owner_id)
|
117
|
+
resource_owner_id = resource_owner_id.try(:id) || resource_owner_id
|
118
|
+
result = where(application_id: application_id, resource_owner_id: resource_owner_id).last
|
119
|
+
return nil unless result
|
120
|
+
result[:revoked_at] ? nil : result
|
121
|
+
end
|
122
|
+
|
123
|
+
# Looking for not expired Access Token with a matching set of scopes
|
124
|
+
# that belongs to specific Application and Resource Owner.
|
125
|
+
#
|
126
|
+
# @param application [Doorkeeper::Application]
|
127
|
+
# Application instance
|
128
|
+
# @param resource_owner_or_id [ActiveRecord::Base, Integer]
|
129
|
+
# Resource Owner model instance or it's ID
|
130
|
+
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
131
|
+
# set of scopes
|
132
|
+
#
|
133
|
+
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
134
|
+
# nil if matching record was not found
|
135
|
+
#
|
136
|
+
def matching_token_for(application, resource_owner_or_id, scopes)
|
137
|
+
resource_owner_id = resource_owner_or_id.try(:id) || resource_owner_or_id
|
138
|
+
token = last_authorized_token_for(application.try(:id), resource_owner_id)
|
139
|
+
if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
|
140
|
+
token
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Checks whether the token scopes match the scopes from the parameters or
|
145
|
+
# Application scopes (if present).
|
146
|
+
#
|
147
|
+
# @param token_scopes [#to_s]
|
148
|
+
# set of scopes (any object that responds to `#to_s`)
|
149
|
+
# @param param_scopes [String]
|
150
|
+
# scopes from params
|
151
|
+
# @param app_scopes [String]
|
152
|
+
# Application scopes
|
153
|
+
#
|
154
|
+
# @return [Boolean] true if all scopes and blank or matches
|
155
|
+
# and false in other cases
|
156
|
+
#
|
157
|
+
def scopes_match?(token_scopes, param_scopes, app_scopes)
|
158
|
+
(!token_scopes.present? && !param_scopes.present?) ||
|
159
|
+
Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
|
160
|
+
token_scopes.to_s,
|
161
|
+
param_scopes,
|
162
|
+
app_scopes
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Looking for not expired AccessToken record with a matching set of
|
167
|
+
# scopes that belongs to specific Application and Resource Owner.
|
168
|
+
# If it doesn't exists - then creates it.
|
169
|
+
#
|
170
|
+
# @param application [Doorkeeper::Application]
|
171
|
+
# Application instance
|
172
|
+
# @param resource_owner_id [ActiveRecord::Base, Integer]
|
173
|
+
# Resource Owner model instance or it's ID
|
174
|
+
# @param scopes [#to_s]
|
175
|
+
# set of scopes (any object that responds to `#to_s`)
|
176
|
+
# @param expires_in [Integer]
|
177
|
+
# token lifetime in seconds
|
178
|
+
# @param use_refresh_token [Boolean]
|
179
|
+
# whether to use the refresh token
|
180
|
+
#
|
181
|
+
# @return [Doorkeeper::AccessToken] existing record or a new one
|
182
|
+
#
|
183
|
+
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
184
|
+
resource_owner = resource_owner.try(:id) || resource_owner
|
185
|
+
|
186
|
+
if Doorkeeper.config.reuse_access_token
|
187
|
+
access_token = matching_token_for(application, resource_owner, scopes)
|
188
|
+
|
189
|
+
return access_token if access_token&.reusable?
|
190
|
+
end
|
191
|
+
|
192
|
+
create!(
|
193
|
+
application_id: application.try(:id),
|
194
|
+
resource_owner_id: resource_owner || application.try(:owner_id),
|
195
|
+
scopes: scopes.to_s,
|
196
|
+
**token_attributes,
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_for(application:, resource_owner:, scopes:, **token_attributes)
|
201
|
+
token_attributes[:application_id] = application&.id
|
202
|
+
token_attributes[:scopes] = scopes.to_s
|
203
|
+
|
204
|
+
resource_owner = resource_owner.try(:id) || resource_owner
|
205
|
+
token_attributes[:resource_owner_id] = resource_owner || application.try(:owner_id)
|
206
|
+
|
207
|
+
create!(**token_attributes)
|
208
|
+
end
|
209
|
+
|
210
|
+
##
|
211
|
+
# Determines the secret storing transformer
|
212
|
+
# Unless configured otherwise, uses the plain secret strategy
|
213
|
+
#
|
214
|
+
# @return [Doorkeeper::SecretStoring::Base]
|
215
|
+
#
|
216
|
+
def secret_strategy
|
217
|
+
::Doorkeeper.config.token_secret_strategy
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Determine the fallback storing strategy
|
222
|
+
# Unless configured, there will be no fallback
|
223
|
+
def fallback_secret_strategy
|
224
|
+
::Doorkeeper.config.token_secret_fallback_strategy
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Access Token type: Bearer.
|
229
|
+
# @see https://tools.ietf.org/html/rfc6750
|
230
|
+
# The OAuth 2.0 Authorization Framework: Bearer Token Usage
|
231
|
+
#
|
232
|
+
def token_type
|
233
|
+
'bearer'
|
234
|
+
end
|
235
|
+
|
236
|
+
def use_refresh_token?
|
237
|
+
@use_refresh_token ||= false
|
238
|
+
!!@use_refresh_token
|
239
|
+
end
|
240
|
+
|
241
|
+
# JSON representation of the Access Token instance.
|
242
|
+
#
|
243
|
+
# @return [Hash] hash with token data
|
244
|
+
def as_json(_options = {})
|
245
|
+
{
|
246
|
+
resource_owner_id: resource_owner_id,
|
247
|
+
scopes: scopes,
|
248
|
+
expires_in_seconds: expires_in_seconds,
|
249
|
+
application: { uid: application.try(:uid) },
|
250
|
+
created_at: created_at.to_i
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
# Indicates whether the token instance have the same credential
|
255
|
+
# as the other Access Token.
|
256
|
+
#
|
257
|
+
# @param access_token [Doorkeeper::AccessToken] other token
|
258
|
+
#
|
259
|
+
# @return [Boolean] true if credentials are same of false in other cases
|
260
|
+
#
|
261
|
+
def same_credential?(access_token)
|
262
|
+
application_id == access_token.application_id &&
|
263
|
+
resource_owner_id == access_token.resource_owner_id
|
264
|
+
end
|
265
|
+
|
266
|
+
# Indicates if token is acceptable for specific scopes.
|
267
|
+
#
|
268
|
+
# @param scopes [Array<String>] scopes
|
269
|
+
#
|
270
|
+
# @return [Boolean] true if record is accessible and includes scopes or
|
271
|
+
# false in other cases
|
272
|
+
#
|
273
|
+
def acceptable?(scopes)
|
274
|
+
accessible? && includes_scope?(*scopes)
|
275
|
+
end
|
276
|
+
|
277
|
+
# We keep a volatile copy of the raw refresh token for initial communication
|
278
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
279
|
+
def plaintext_refresh_token
|
280
|
+
if secret_strategy.allows_restoring_secrets?
|
281
|
+
secret_strategy.restore_secret(self, :refresh_token)
|
282
|
+
else
|
283
|
+
@raw_refresh_token
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# We keep a volatile copy of the raw token for initial communication
|
288
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
289
|
+
#
|
290
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric encryption)
|
291
|
+
# while hashing strategies do not, so you cannot rely on this value
|
292
|
+
# returning a present value for persisted tokens.
|
293
|
+
def plaintext_token
|
294
|
+
if secret_strategy.allows_restoring_secrets?
|
295
|
+
secret_strategy.restore_secret(self, :token)
|
296
|
+
else
|
297
|
+
@raw_token
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Revokes token with `:refresh_token` equal to `:previous_refresh_token`
|
302
|
+
# and clears `:previous_refresh_token` attribute.
|
303
|
+
#
|
304
|
+
def revoke_previous_refresh_token!
|
305
|
+
return unless self.class.refresh_token_revoked_on_use?
|
306
|
+
self.previous_refresh_token = ''
|
307
|
+
self.save!
|
308
|
+
end
|
309
|
+
|
310
|
+
def revoke(clock = Time)
|
311
|
+
self.revoked_at = clock.now.utc
|
312
|
+
self.save!
|
313
|
+
end
|
314
|
+
|
315
|
+
def transaction; yield; end
|
316
|
+
def lock!; end
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
before_validation :generate_token, on: :create
|
321
|
+
before_validation :generate_refresh_token, on: :create, if: :use_refresh_token?
|
322
|
+
|
323
|
+
validate :refresh_token_unique
|
324
|
+
|
325
|
+
def refresh_token_unique
|
326
|
+
if refresh_token_changed? && refresh_token.present? && ::Doorkeeper::AccessToken.where(refresh_token: refresh_token).count > 0
|
327
|
+
errors.add(:refresh_token, "must be unique")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# Generates refresh token with UniqueToken generator.
|
332
|
+
#
|
333
|
+
# @return [String] refresh token value
|
334
|
+
#
|
335
|
+
def generate_refresh_token
|
336
|
+
if self.refresh_token.blank?
|
337
|
+
@raw_refresh_token = UniqueToken.generate
|
338
|
+
secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def generate_token
|
343
|
+
return if self.token.present?
|
344
|
+
|
345
|
+
self.created_at ||= Time.now.utc
|
346
|
+
|
347
|
+
if use_refresh_token?
|
348
|
+
self.ttl = (self.created_at + 1.months).to_i
|
349
|
+
else
|
350
|
+
self.ttl = (self.created_at + self.expires_in + 30).to_i
|
351
|
+
end
|
352
|
+
|
353
|
+
generator = Doorkeeper.configuration.access_token_generator.constantize
|
354
|
+
@raw_token = generator.generate(
|
355
|
+
resource_owner_id: resource_owner_id,
|
356
|
+
scopes: scopes,
|
357
|
+
application: application,
|
358
|
+
expires_in: expires_in,
|
359
|
+
created_at: created_at
|
360
|
+
)
|
361
|
+
secret_strategy.store_secret(self, :token, @raw_token)
|
362
|
+
@raw_token
|
363
|
+
rescue NoMethodError
|
364
|
+
raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
|
365
|
+
rescue NameError
|
366
|
+
raise Errors::TokenGeneratorNotFound, "#{generator} not found"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# # Skips validation if field is empty
|
371
|
+
# class UniqueOrEmptyValidator < UniquenessValidator
|
372
|
+
# def validate_each(doc, attr, value)
|
373
|
+
# super if value
|
374
|
+
# end
|
375
|
+
# end
|
376
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
# Load these before the has_many
|
3
|
+
require File.expand_path("../access_grant", __FILE__)
|
4
|
+
require File.expand_path("../access_token", __FILE__)
|
5
|
+
|
6
|
+
# Required for Rails 6 support
|
7
|
+
require "doorkeeper/orm/active_record/redirect_uri_validator"
|
8
|
+
|
9
|
+
module Doorkeeper
|
10
|
+
class Application
|
11
|
+
include NoBrainer::Document
|
12
|
+
|
13
|
+
table_config name: 'doorkeeper_app'
|
14
|
+
|
15
|
+
include OAuth::Helpers
|
16
|
+
include Models::Scopes
|
17
|
+
|
18
|
+
field :name, type: String
|
19
|
+
field :uid, type: String, uniq: true, index: true
|
20
|
+
field :secret, type: String
|
21
|
+
field :scopes, type: String
|
22
|
+
field :redirect_uri, type: String, uniq: true, index: true
|
23
|
+
|
24
|
+
field :skip_authorization, type: Boolean, default: false
|
25
|
+
field :confidential, type: Boolean, default: false
|
26
|
+
|
27
|
+
field :owner_id, type: String
|
28
|
+
|
29
|
+
validates :owner, presence: true, if: :validate_owner?
|
30
|
+
def validate_owner?
|
31
|
+
Doorkeeper.configuration.confirm_application_owner?
|
32
|
+
end
|
33
|
+
|
34
|
+
has_many :access_grants, dependent: :destroy, class_name: 'Doorkeeper::AccessGrant'
|
35
|
+
has_many :access_tokens, dependent: :destroy, class_name: 'Doorkeeper::AccessToken'
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def by_uid(uid)
|
39
|
+
where(uid: uid).first
|
40
|
+
end
|
41
|
+
|
42
|
+
def by_uid_and_secret(uid, secret)
|
43
|
+
where(uid: uid, secret: secret).first
|
44
|
+
end
|
45
|
+
|
46
|
+
def by_uid_and_secret(uid, secret)
|
47
|
+
app = by_uid(uid)
|
48
|
+
return unless app
|
49
|
+
return app if secret.blank? && !app.confidential?
|
50
|
+
return unless app.secret_matches?(secret)
|
51
|
+
|
52
|
+
app
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorized_for(resource_owner)
|
56
|
+
AccessToken.find_by_resource_owner_id(resource_owner.id).collect(&:application)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def authorized_for_resource_owner?(resource_owner)
|
61
|
+
Doorkeeper.configuration.authorize_resource_owner_for_client.call(self, resource_owner)
|
62
|
+
end
|
63
|
+
|
64
|
+
def secret_matches?(input)
|
65
|
+
# return false if either is nil, since secure_compare depends on strings
|
66
|
+
# but Application secrets MAY be nil depending on confidentiality.
|
67
|
+
return false if input.nil? || secret.nil?
|
68
|
+
|
69
|
+
# When matching the secret by comparer function, all is well.
|
70
|
+
input == secret
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
validates :name, :secret, :uid, presence: true
|
76
|
+
validates :redirect_uri, "doorkeeper/redirect_uri": true
|
77
|
+
validates :confidential, inclusion: { in: [true, false] }
|
78
|
+
|
79
|
+
before_validation :generate_uid, :generate_secret, on: :create
|
80
|
+
|
81
|
+
def has_scopes?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def generate_uid
|
86
|
+
self.uid = UniqueToken.generate if uid.blank?
|
87
|
+
end
|
88
|
+
|
89
|
+
def generate_secret
|
90
|
+
self.secret = UniqueToken.generate if secret.blank?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Doorkeeper
|
3
|
+
# Reopen to align with NoBrainer's exclusively `super` overrides.
|
4
|
+
# ORM is thrust into an infinite loop, otherwise.
|
5
|
+
module Models::Scopes
|
6
|
+
def scopes
|
7
|
+
OAuth::Scopes.from_string(super)
|
8
|
+
end
|
9
|
+
|
10
|
+
def scopes=(value)
|
11
|
+
super Array(value).join(" ")
|
12
|
+
end
|
13
|
+
|
14
|
+
def scopes_string
|
15
|
+
self._read_attribute(:scopes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def includes_scope?(*required_scopes)
|
19
|
+
required_scopes.blank? || required_scopes.any? { |scope| scopes.exists?(scope.to_s) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module Rethinkdb
|
5
|
+
module Timestamps
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
field :revoked_at, type: Integer
|
10
|
+
field :created_at, type: Integer, default: ->{ Time.now.to_i + 1 }
|
11
|
+
end
|
12
|
+
|
13
|
+
def revoked_at
|
14
|
+
revoked = super
|
15
|
+
Time.at(revoked) unless revoked.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def revoked_at=(time)
|
19
|
+
if time
|
20
|
+
number = time.is_a?(Numeric) ? time.to_i : time.to_time.to_i
|
21
|
+
super number
|
22
|
+
else
|
23
|
+
super nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def created_at
|
28
|
+
Time.at(super)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: doorkeeper-rethinkdb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen von Takach
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: doorkeeper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.0.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: nobrainer
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.33.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.33.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: sqlite3
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: capybara
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: database_cleaner
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '1.5'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '1.5'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: factory_girl
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '4.7'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '4.7'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: generator_spec
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0.9'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0.9'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: rake
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 12.3.3
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 12.3.3
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: rspec-rails
|
139
|
+
requirement: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - "~>"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
type: :development
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - "~>"
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
- !ruby/object:Gem::Dependency
|
152
|
+
name: timecop
|
153
|
+
requirement: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - "~>"
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0.8'
|
158
|
+
type: :development
|
159
|
+
prerelease: false
|
160
|
+
version_requirements: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - "~>"
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0.8'
|
165
|
+
description: Doorkeeper RethinkDB ORMs
|
166
|
+
email:
|
167
|
+
- steve@aca.im
|
168
|
+
executables: []
|
169
|
+
extensions: []
|
170
|
+
extra_rdoc_files: []
|
171
|
+
files:
|
172
|
+
- MIT-LICENSE
|
173
|
+
- README.md
|
174
|
+
- Rakefile
|
175
|
+
- lib/doorkeeper-rethinkdb.rb
|
176
|
+
- lib/doorkeeper-rethinkdb/version.rb
|
177
|
+
- lib/support/orm/rethinkdb.rb
|
178
|
+
- lib/support/orm/rethinkdb/access_grant.rb
|
179
|
+
- lib/support/orm/rethinkdb/access_token.rb
|
180
|
+
- lib/support/orm/rethinkdb/application.rb
|
181
|
+
- lib/support/orm/rethinkdb/scopes.rb
|
182
|
+
- lib/support/orm/rethinkdb/timestamps.rb
|
183
|
+
homepage: http://github.com/aca-labs/doorkeeper-rethinkdb
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata: {}
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
requirements: []
|
202
|
+
rubyforge_project:
|
203
|
+
rubygems_version: 2.7.7
|
204
|
+
signing_key:
|
205
|
+
specification_version: 4
|
206
|
+
summary: Doorkeeper RethinkDB ORMs
|
207
|
+
test_files: []
|