doorkeeper 4.2.6 → 5.5.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of doorkeeper might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1049 -0
- data/README.md +110 -353
- data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
- data/app/controllers/doorkeeper/application_controller.rb +6 -7
- data/app/controllers/doorkeeper/application_metal_controller.rb +7 -11
- data/app/controllers/doorkeeper/applications_controller.rb +65 -16
- data/app/controllers/doorkeeper/authorizations_controller.rb +97 -17
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +22 -3
- data/app/controllers/doorkeeper/token_info_controller.rb +16 -4
- data/app/controllers/doorkeeper/tokens_controller.rb +115 -38
- data/app/helpers/doorkeeper/dashboard_helper.rb +10 -6
- data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
- data/app/views/doorkeeper/applications/_form.html.erb +33 -21
- data/app/views/doorkeeper/applications/edit.html.erb +1 -1
- data/app/views/doorkeeper/applications/index.html.erb +18 -6
- data/app/views/doorkeeper/applications/new.html.erb +1 -1
- data/app/views/doorkeeper/applications/show.html.erb +40 -16
- data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
- data/app/views/doorkeeper/authorizations/form_post.html.erb +15 -0
- data/app/views/doorkeeper/authorizations/new.html.erb +6 -0
- data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +16 -14
- data/config/locales/en.yml +34 -7
- data/lib/doorkeeper/config/abstract_builder.rb +28 -0
- data/lib/doorkeeper/config/option.rb +82 -0
- data/lib/doorkeeper/config/validations.rb +53 -0
- data/lib/doorkeeper/config.rb +514 -167
- data/lib/doorkeeper/engine.rb +11 -5
- data/lib/doorkeeper/errors.rb +25 -16
- data/lib/doorkeeper/grant_flow/fallback_flow.rb +15 -0
- data/lib/doorkeeper/grant_flow/flow.rb +44 -0
- data/lib/doorkeeper/grant_flow/registry.rb +50 -0
- data/lib/doorkeeper/grant_flow.rb +45 -0
- data/lib/doorkeeper/grape/authorization_decorator.rb +6 -4
- data/lib/doorkeeper/grape/helpers.rb +23 -12
- data/lib/doorkeeper/helpers/controller.rb +51 -14
- data/lib/doorkeeper/models/access_grant_mixin.rb +94 -27
- data/lib/doorkeeper/models/access_token_mixin.rb +284 -96
- data/lib/doorkeeper/models/application_mixin.rb +58 -27
- data/lib/doorkeeper/models/concerns/accessible.rb +2 -0
- data/lib/doorkeeper/models/concerns/expirable.rb +12 -6
- data/lib/doorkeeper/models/concerns/orderable.rb +15 -0
- data/lib/doorkeeper/models/concerns/ownership.rb +4 -7
- data/lib/doorkeeper/models/concerns/resource_ownerable.rb +47 -0
- data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
- data/lib/doorkeeper/models/concerns/revocable.rb +3 -27
- data/lib/doorkeeper/models/concerns/scopes.rb +12 -2
- data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +48 -12
- data/lib/doorkeeper/oauth/authorization/context.rb +17 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +66 -28
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +7 -5
- data/lib/doorkeeper/oauth/authorization_code_request.rb +63 -10
- data/lib/doorkeeper/oauth/base_request.rb +35 -19
- data/lib/doorkeeper/oauth/base_response.rb +2 -0
- data/lib/doorkeeper/oauth/client/credentials.rb +9 -7
- data/lib/doorkeeper/oauth/client.rb +10 -11
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +47 -4
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +16 -9
- data/lib/doorkeeper/oauth/client_credentials/validator.rb +56 -0
- data/lib/doorkeeper/oauth/client_credentials_request.rb +10 -11
- data/lib/doorkeeper/oauth/code_request.rb +8 -12
- data/lib/doorkeeper/oauth/code_response.rb +27 -15
- data/lib/doorkeeper/oauth/error.rb +5 -3
- data/lib/doorkeeper/oauth/error_response.rb +35 -15
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +11 -3
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -18
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +20 -3
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +53 -3
- data/lib/doorkeeper/oauth/hooks/context.rb +21 -0
- data/lib/doorkeeper/oauth/invalid_request_response.rb +43 -0
- data/lib/doorkeeper/oauth/invalid_token_response.rb +29 -5
- data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +44 -10
- data/lib/doorkeeper/oauth/pre_authorization.rb +135 -26
- data/lib/doorkeeper/oauth/refresh_token_request.rb +60 -31
- data/lib/doorkeeper/oauth/scopes.rb +26 -12
- data/lib/doorkeeper/oauth/token.rb +13 -9
- data/lib/doorkeeper/oauth/token_introspection.rb +202 -0
- data/lib/doorkeeper/oauth/token_request.rb +8 -20
- data/lib/doorkeeper/oauth/token_response.rb +14 -10
- data/lib/doorkeeper/oauth.rb +13 -0
- data/lib/doorkeeper/orm/active_record/access_grant.rb +6 -4
- data/lib/doorkeeper/orm/active_record/access_token.rb +5 -42
- data/lib/doorkeeper/orm/active_record/application.rb +6 -20
- data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +69 -0
- data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +60 -0
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +199 -0
- data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +66 -0
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +33 -0
- data/lib/doorkeeper/orm/active_record.rb +37 -8
- data/lib/doorkeeper/rails/helpers.rb +14 -13
- data/lib/doorkeeper/rails/routes/abstract_router.rb +35 -0
- data/lib/doorkeeper/rails/routes/mapper.rb +4 -2
- data/lib/doorkeeper/rails/routes/mapping.rb +9 -7
- data/lib/doorkeeper/rails/routes/registry.rb +45 -0
- data/lib/doorkeeper/rails/routes.rb +41 -28
- data/lib/doorkeeper/rake/db.rake +40 -0
- data/lib/doorkeeper/rake/setup.rake +11 -0
- data/lib/doorkeeper/rake.rb +14 -0
- data/lib/doorkeeper/request/authorization_code.rb +6 -4
- data/lib/doorkeeper/request/client_credentials.rb +3 -3
- data/lib/doorkeeper/request/code.rb +1 -1
- data/lib/doorkeeper/request/password.rb +5 -14
- data/lib/doorkeeper/request/refresh_token.rb +6 -5
- data/lib/doorkeeper/request/strategy.rb +4 -2
- data/lib/doorkeeper/request/token.rb +1 -1
- data/lib/doorkeeper/request.rb +62 -29
- data/lib/doorkeeper/secret_storing/base.rb +64 -0
- data/lib/doorkeeper/secret_storing/bcrypt.rb +60 -0
- data/lib/doorkeeper/secret_storing/plain.rb +33 -0
- data/lib/doorkeeper/secret_storing/sha256_hash.rb +26 -0
- data/lib/doorkeeper/server.rb +9 -11
- data/lib/doorkeeper/stale_records_cleaner.rb +24 -0
- data/lib/doorkeeper/validations.rb +5 -2
- data/lib/doorkeeper/version.rb +12 -1
- data/lib/doorkeeper.rb +111 -62
- data/lib/generators/doorkeeper/application_owner_generator.rb +28 -13
- data/lib/generators/doorkeeper/confidential_applications_generator.rb +33 -0
- data/lib/generators/doorkeeper/enable_polymorphic_resource_owner_generator.rb +39 -0
- data/lib/generators/doorkeeper/install_generator.rb +19 -9
- data/lib/generators/doorkeeper/migration_generator.rb +27 -10
- data/lib/generators/doorkeeper/pkce_generator.rb +33 -0
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -19
- data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
- data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb +9 -0
- data/{spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb → lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erb} +3 -1
- data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +8 -0
- data/lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.erb +17 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +412 -33
- data/lib/generators/doorkeeper/templates/migration.rb.erb +88 -0
- data/lib/generators/doorkeeper/views_generator.rb +8 -4
- data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
- metadata +114 -276
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -19
- data/.hound.yml +0 -13
- data/.rspec +0 -1
- data/.travis.yml +0 -26
- data/Appraisals +0 -14
- data/CONTRIBUTING.md +0 -47
- data/Gemfile +0 -10
- data/NEWS.md +0 -606
- data/RELEASING.md +0 -10
- data/Rakefile +0 -20
- data/app/validators/redirect_uri_validator.rb +0 -34
- data/doorkeeper.gemspec +0 -29
- data/gemfiles/rails_4_2.gemfile +0 -11
- data/gemfiles/rails_5_0.gemfile +0 -12
- data/gemfiles/rails_5_1.gemfile +0 -13
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +0 -45
- data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb +0 -7
- data/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb +0 -11
- data/lib/generators/doorkeeper/templates/migration.rb +0 -68
- data/spec/controllers/application_metal_controller.rb +0 -10
- data/spec/controllers/applications_controller_spec.rb +0 -58
- data/spec/controllers/authorizations_controller_spec.rb +0 -218
- data/spec/controllers/protected_resources_controller_spec.rb +0 -300
- data/spec/controllers/token_info_controller_spec.rb +0 -52
- data/spec/controllers/tokens_controller_spec.rb +0 -88
- data/spec/dummy/Rakefile +0 -7
- data/spec/dummy/app/controllers/application_controller.rb +0 -3
- data/spec/dummy/app/controllers/custom_authorizations_controller.rb +0 -7
- data/spec/dummy/app/controllers/full_protected_resources_controller.rb +0 -12
- data/spec/dummy/app/controllers/home_controller.rb +0 -17
- data/spec/dummy/app/controllers/metal_controller.rb +0 -11
- data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +0 -11
- data/spec/dummy/app/helpers/application_helper.rb +0 -5
- data/spec/dummy/app/models/user.rb +0 -5
- data/spec/dummy/app/views/home/index.html.erb +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +0 -14
- data/spec/dummy/config/application.rb +0 -23
- data/spec/dummy/config/boot.rb +0 -9
- data/spec/dummy/config/database.yml +0 -15
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -29
- data/spec/dummy/config/environments/production.rb +0 -62
- data/spec/dummy/config/environments/test.rb +0 -44
- data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +0 -6
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/doorkeeper.rb +0 -96
- data/spec/dummy/config/initializers/secret_token.rb +0 -9
- data/spec/dummy/config/initializers/session_store.rb +0 -8
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/doorkeeper.en.yml +0 -5
- data/spec/dummy/config/routes.rb +0 -52
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/db/migrate/20111122132257_create_users.rb +0 -9
- data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +0 -5
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +0 -60
- data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +0 -7
- data/spec/dummy/db/schema.rb +0 -67
- data/spec/dummy/public/404.html +0 -26
- data/spec/dummy/public/422.html +0 -26
- data/spec/dummy/public/500.html +0 -26
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +0 -6
- data/spec/factories.rb +0 -28
- data/spec/generators/application_owner_generator_spec.rb +0 -22
- data/spec/generators/install_generator_spec.rb +0 -31
- data/spec/generators/migration_generator_spec.rb +0 -20
- data/spec/generators/templates/routes.rb +0 -3
- data/spec/generators/views_generator_spec.rb +0 -27
- data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +0 -24
- data/spec/lib/config_spec.rb +0 -334
- data/spec/lib/doorkeeper_spec.rb +0 -150
- data/spec/lib/models/expirable_spec.rb +0 -50
- data/spec/lib/models/revocable_spec.rb +0 -59
- data/spec/lib/models/scopes_spec.rb +0 -43
- data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -41
- data/spec/lib/oauth/authorization_code_request_spec.rb +0 -80
- data/spec/lib/oauth/base_request_spec.rb +0 -160
- data/spec/lib/oauth/base_response_spec.rb +0 -45
- data/spec/lib/oauth/client/credentials_spec.rb +0 -88
- data/spec/lib/oauth/client_credentials/creator_spec.rb +0 -44
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +0 -86
- data/spec/lib/oauth/client_credentials/validation_spec.rb +0 -54
- data/spec/lib/oauth/client_credentials_integration_spec.rb +0 -27
- data/spec/lib/oauth/client_credentials_request_spec.rb +0 -104
- data/spec/lib/oauth/client_spec.rb +0 -39
- data/spec/lib/oauth/code_request_spec.rb +0 -45
- data/spec/lib/oauth/code_response_spec.rb +0 -34
- data/spec/lib/oauth/error_response_spec.rb +0 -61
- data/spec/lib/oauth/error_spec.rb +0 -23
- data/spec/lib/oauth/forbidden_token_response_spec.rb +0 -23
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -64
- data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -20
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +0 -104
- data/spec/lib/oauth/invalid_token_response_spec.rb +0 -56
- data/spec/lib/oauth/password_access_token_request_spec.rb +0 -90
- data/spec/lib/oauth/pre_authorization_spec.rb +0 -155
- data/spec/lib/oauth/refresh_token_request_spec.rb +0 -154
- data/spec/lib/oauth/scopes_spec.rb +0 -122
- data/spec/lib/oauth/token_request_spec.rb +0 -98
- data/spec/lib/oauth/token_response_spec.rb +0 -85
- data/spec/lib/oauth/token_spec.rb +0 -116
- data/spec/lib/request/strategy_spec.rb +0 -53
- data/spec/lib/server_spec.rb +0 -49
- data/spec/models/doorkeeper/access_grant_spec.rb +0 -36
- data/spec/models/doorkeeper/access_token_spec.rb +0 -394
- data/spec/models/doorkeeper/application_spec.rb +0 -179
- data/spec/requests/applications/applications_request_spec.rb +0 -94
- data/spec/requests/applications/authorized_applications_spec.rb +0 -30
- data/spec/requests/endpoints/authorization_spec.rb +0 -71
- data/spec/requests/endpoints/token_spec.rb +0 -64
- data/spec/requests/flows/authorization_code_errors_spec.rb +0 -76
- data/spec/requests/flows/authorization_code_spec.rb +0 -148
- data/spec/requests/flows/client_credentials_spec.rb +0 -58
- data/spec/requests/flows/implicit_grant_errors_spec.rb +0 -32
- data/spec/requests/flows/implicit_grant_spec.rb +0 -61
- data/spec/requests/flows/password_spec.rb +0 -115
- data/spec/requests/flows/refresh_token_spec.rb +0 -174
- data/spec/requests/flows/revoke_token_spec.rb +0 -157
- data/spec/requests/flows/skip_authorization_spec.rb +0 -59
- data/spec/requests/protected_resources/metal_spec.rb +0 -14
- data/spec/requests/protected_resources/private_api_spec.rb +0 -81
- data/spec/routing/custom_controller_routes_spec.rb +0 -71
- data/spec/routing/default_routes_spec.rb +0 -35
- data/spec/routing/scoped_routes_spec.rb +0 -31
- data/spec/spec_helper.rb +0 -4
- data/spec/spec_helper_integration.rb +0 -63
- data/spec/support/dependencies/factory_girl.rb +0 -2
- data/spec/support/helpers/access_token_request_helper.rb +0 -11
- data/spec/support/helpers/authorization_request_helper.rb +0 -41
- data/spec/support/helpers/config_helper.rb +0 -9
- data/spec/support/helpers/model_helper.rb +0 -67
- data/spec/support/helpers/request_spec_helper.rb +0 -84
- data/spec/support/helpers/url_helper.rb +0 -55
- data/spec/support/http_method_shim.rb +0 -38
- data/spec/support/orm/active_record.rb +0 -3
- data/spec/support/shared/controllers_shared_context.rb +0 -69
- data/spec/support/shared/models_shared_examples.rb +0 -52
- data/spec/validators/redirect_uri_validator_spec.rb +0 -78
@@ -1,50 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Doorkeeper
|
2
4
|
module AccessTokenMixin
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
include OAuth::Helpers
|
6
8
|
include Models::Expirable
|
9
|
+
include Models::Reusable
|
7
10
|
include Models::Revocable
|
8
11
|
include Models::Accessible
|
12
|
+
include Models::Orderable
|
13
|
+
include Models::SecretStorable
|
9
14
|
include Models::Scopes
|
10
|
-
include
|
11
|
-
|
12
|
-
included do
|
13
|
-
belongs_to_options = {
|
14
|
-
class_name: 'Doorkeeper::Application',
|
15
|
-
inverse_of: :access_tokens
|
16
|
-
}
|
17
|
-
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
|
18
|
-
belongs_to_options[:optional] = true
|
19
|
-
end
|
20
|
-
|
21
|
-
belongs_to :application, belongs_to_options
|
22
|
-
|
23
|
-
validates :token, presence: true, uniqueness: true
|
24
|
-
validates :refresh_token, uniqueness: true, if: :use_refresh_token?
|
25
|
-
|
26
|
-
# @attr_writer [Boolean, nil] use_refresh_token
|
27
|
-
# indicates the possibility of using refresh token
|
28
|
-
attr_writer :use_refresh_token
|
29
|
-
|
30
|
-
before_validation :generate_token, on: :create
|
31
|
-
before_validation :generate_refresh_token,
|
32
|
-
on: :create,
|
33
|
-
if: :use_refresh_token?
|
34
|
-
end
|
15
|
+
include Models::ResourceOwnerable
|
35
16
|
|
36
17
|
module ClassMethods
|
37
18
|
# Returns an instance of the Doorkeeper::AccessToken with
|
38
|
-
# specific token value.
|
19
|
+
# specific plain text token value.
|
39
20
|
#
|
40
21
|
# @param token [#to_s]
|
41
|
-
# token value (any object that responds to `#to_s`)
|
22
|
+
# Plain text token value (any object that responds to `#to_s`)
|
42
23
|
#
|
43
24
|
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
44
25
|
# if there is no record with such token
|
45
26
|
#
|
46
27
|
def by_token(token)
|
47
|
-
|
28
|
+
find_by_plaintext_token(:token, token)
|
48
29
|
end
|
49
30
|
|
50
31
|
# Returns an instance of the Doorkeeper::AccessToken
|
@@ -57,7 +38,22 @@ module Doorkeeper
|
|
57
38
|
# if there is no record with such refresh token
|
58
39
|
#
|
59
40
|
def by_refresh_token(refresh_token)
|
60
|
-
|
41
|
+
find_by_plaintext_token(:refresh_token, refresh_token)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns an instance of the Doorkeeper::AccessToken
|
45
|
+
# found by previous refresh token. Keep in mind that value
|
46
|
+
# of the previous_refresh_token isn't encrypted using
|
47
|
+
# secrets strategy.
|
48
|
+
#
|
49
|
+
# @param previous_refresh_token [#to_s]
|
50
|
+
# previous refresh token value (any object that responds to `#to_s`)
|
51
|
+
#
|
52
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
53
|
+
# if there is no record with such refresh token
|
54
|
+
#
|
55
|
+
def by_previous_refresh_token(previous_refresh_token)
|
56
|
+
find_by(refresh_token: previous_refresh_token)
|
61
57
|
end
|
62
58
|
|
63
59
|
# Revokes AccessToken records that have not been revoked and associated
|
@@ -65,22 +61,24 @@ module Doorkeeper
|
|
65
61
|
#
|
66
62
|
# @param application_id [Integer]
|
67
63
|
# ID of the Application
|
68
|
-
# @param resource_owner [ActiveRecord::Base]
|
69
|
-
# instance of the Resource Owner model
|
70
|
-
#
|
71
|
-
def revoke_all_for(application_id, resource_owner)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
64
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
65
|
+
# instance of the Resource Owner model or it's ID
|
66
|
+
#
|
67
|
+
def revoke_all_for(application_id, resource_owner, clock = Time)
|
68
|
+
by_resource_owner(resource_owner)
|
69
|
+
.where(
|
70
|
+
application_id: application_id,
|
71
|
+
revoked_at: nil,
|
72
|
+
)
|
73
|
+
.update_all(revoked_at: clock.now.utc)
|
76
74
|
end
|
77
75
|
|
78
|
-
# Looking for not
|
76
|
+
# Looking for not revoked Access Token with a matching set of scopes
|
79
77
|
# that belongs to specific Application and Resource Owner.
|
80
78
|
#
|
81
79
|
# @param application [Doorkeeper::Application]
|
82
80
|
# Application instance
|
83
|
-
# @param
|
81
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
84
82
|
# Resource Owner model instance or it's ID
|
85
83
|
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
86
84
|
# set of scopes
|
@@ -88,37 +86,77 @@ module Doorkeeper
|
|
88
86
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
89
87
|
# nil if matching record was not found
|
90
88
|
#
|
91
|
-
def matching_token_for(application,
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
89
|
+
def matching_token_for(application, resource_owner, scopes)
|
90
|
+
tokens = authorized_tokens_for(application&.id, resource_owner)
|
91
|
+
find_matching_token(tokens, application, scopes)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Interface to enumerate access token records in batches in order not
|
95
|
+
# to bloat the memory. Could be overloaded in any ORM extension.
|
96
|
+
#
|
97
|
+
def find_access_token_in_batches(relation, **args, &block)
|
98
|
+
relation.find_in_batches(**args, &block)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Enumerates AccessToken records in batches to find a matching token.
|
102
|
+
# Batching is required in order not to pollute the memory if Application
|
103
|
+
# has huge amount of associated records.
|
104
|
+
#
|
105
|
+
# ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
|
106
|
+
# database sort by created_at, so we need to load all the matching records,
|
107
|
+
# sort them and find latest one. Probably it would be better to rewrite this
|
108
|
+
# query using Time math if possible, but we n eed to consider ORM and
|
109
|
+
# different databases support.
|
110
|
+
#
|
111
|
+
# @param relation [ActiveRecord::Relation]
|
112
|
+
# Access tokens relation
|
113
|
+
# @param application [Doorkeeper::Application]
|
114
|
+
# Application instance
|
115
|
+
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
116
|
+
# set of scopes
|
117
|
+
#
|
118
|
+
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
119
|
+
# nil if matching record was not found
|
120
|
+
#
|
121
|
+
def find_matching_token(relation, application, scopes)
|
122
|
+
return nil unless relation
|
123
|
+
|
124
|
+
matching_tokens = []
|
125
|
+
batch_size = Doorkeeper.configuration.token_lookup_batch_size
|
126
|
+
|
127
|
+
find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
|
128
|
+
tokens = batch.select do |token|
|
129
|
+
scopes_match?(token.scopes, scopes, application&.scopes)
|
130
|
+
end
|
131
|
+
|
132
|
+
matching_tokens.concat(tokens)
|
100
133
|
end
|
134
|
+
|
135
|
+
matching_tokens.max_by(&:created_at)
|
101
136
|
end
|
102
137
|
|
103
|
-
# Checks whether the token scopes match the scopes from the parameters
|
104
|
-
# Application scopes (if present).
|
138
|
+
# Checks whether the token scopes match the scopes from the parameters
|
105
139
|
#
|
106
140
|
# @param token_scopes [#to_s]
|
107
141
|
# set of scopes (any object that responds to `#to_s`)
|
108
|
-
# @param param_scopes [
|
142
|
+
# @param param_scopes [Doorkeeper::OAuth::Scopes]
|
109
143
|
# scopes from params
|
110
|
-
# @param app_scopes [
|
144
|
+
# @param app_scopes [Doorkeeper::OAuth::Scopes]
|
111
145
|
# Application scopes
|
112
146
|
#
|
113
|
-
# @return [Boolean] true if
|
147
|
+
# @return [Boolean] true if the param scopes match the token scopes,
|
148
|
+
# and all the param scopes are defined in the application (or in the
|
149
|
+
# server configuration if the application doesn't define any scopes),
|
114
150
|
# and false in other cases
|
115
151
|
#
|
116
152
|
def scopes_match?(token_scopes, param_scopes, app_scopes)
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
153
|
+
return true if token_scopes.empty? && param_scopes.empty?
|
154
|
+
|
155
|
+
(token_scopes.sort == param_scopes.sort) &&
|
156
|
+
Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
|
157
|
+
scope_str: param_scopes.to_s,
|
158
|
+
server_scopes: Doorkeeper.config.scopes,
|
159
|
+
app_scopes: app_scopes,
|
122
160
|
)
|
123
161
|
end
|
124
162
|
|
@@ -128,59 +166,124 @@ module Doorkeeper
|
|
128
166
|
#
|
129
167
|
# @param application [Doorkeeper::Application]
|
130
168
|
# Application instance
|
131
|
-
# @param
|
169
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
132
170
|
# Resource Owner model instance or it's ID
|
133
171
|
# @param scopes [#to_s]
|
134
172
|
# set of scopes (any object that responds to `#to_s`)
|
135
|
-
# @param
|
173
|
+
# @param token_attributes [Hash]
|
174
|
+
# Additional attributes to use when creating a token
|
175
|
+
# @option token_attributes [Integer] :expires_in
|
136
176
|
# token lifetime in seconds
|
137
|
-
# @
|
177
|
+
# @option token_attributes [Boolean] :use_refresh_token
|
138
178
|
# whether to use the refresh token
|
139
179
|
#
|
140
180
|
# @return [Doorkeeper::AccessToken] existing record or a new one
|
141
181
|
#
|
142
|
-
def find_or_create_for(application
|
143
|
-
if Doorkeeper.
|
144
|
-
access_token = matching_token_for(application,
|
145
|
-
|
146
|
-
|
147
|
-
end
|
182
|
+
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
183
|
+
if Doorkeeper.config.reuse_access_token
|
184
|
+
access_token = matching_token_for(application, resource_owner, scopes)
|
185
|
+
|
186
|
+
return access_token if access_token&.reusable?
|
148
187
|
end
|
149
188
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
scopes:
|
154
|
-
|
155
|
-
use_refresh_token: use_refresh_token
|
189
|
+
create_for(
|
190
|
+
application: application,
|
191
|
+
resource_owner: resource_owner,
|
192
|
+
scopes: scopes,
|
193
|
+
**token_attributes,
|
156
194
|
)
|
157
195
|
end
|
158
196
|
|
159
|
-
#
|
197
|
+
# Creates a not expired AccessToken record with a matching set of
|
198
|
+
# scopes that belongs to specific Application and Resource Owner.
|
199
|
+
#
|
200
|
+
# @param application [Doorkeeper::Application]
|
201
|
+
# Application instance
|
202
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
203
|
+
# Resource Owner model instance or it's ID
|
204
|
+
# @param scopes [#to_s]
|
205
|
+
# set of scopes (any object that responds to `#to_s`)
|
206
|
+
# @param token_attributes [Hash]
|
207
|
+
# Additional attributes to use when creating a token
|
208
|
+
# @option token_attributes [Integer] :expires_in
|
209
|
+
# token lifetime in seconds
|
210
|
+
# @option token_attributes [Boolean] :use_refresh_token
|
211
|
+
# whether to use the refresh token
|
212
|
+
#
|
213
|
+
# @return [Doorkeeper::AccessToken] new access token
|
214
|
+
#
|
215
|
+
def create_for(application:, resource_owner:, scopes:, **token_attributes)
|
216
|
+
token_attributes[:application_id] = application&.id
|
217
|
+
token_attributes[:scopes] = scopes.to_s
|
218
|
+
|
219
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
220
|
+
token_attributes[:resource_owner] = resource_owner
|
221
|
+
else
|
222
|
+
token_attributes[:resource_owner_id] = resource_owner_id_for(resource_owner)
|
223
|
+
end
|
224
|
+
|
225
|
+
create!(token_attributes)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Looking for not revoked Access Token records that belongs to specific
|
160
229
|
# Application and Resource Owner.
|
161
230
|
#
|
162
231
|
# @param application_id [Integer]
|
163
232
|
# ID of the Application model instance
|
164
|
-
# @param
|
233
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
234
|
+
# Resource Owner model instance or it's ID
|
235
|
+
#
|
236
|
+
# @return [ActiveRecord::Relation]
|
237
|
+
# collection of matching AccessToken objects
|
238
|
+
#
|
239
|
+
def authorized_tokens_for(application_id, resource_owner)
|
240
|
+
by_resource_owner(resource_owner).where(
|
241
|
+
application_id: application_id,
|
242
|
+
revoked_at: nil,
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Convenience method for backwards-compatibility, return the last
|
247
|
+
# matching token for the given Application and Resource Owner.
|
248
|
+
#
|
249
|
+
# @param application_id [Integer]
|
250
|
+
# ID of the Application model instance
|
251
|
+
# @param resource_owner [ActiveRecord::Base, Integer]
|
165
252
|
# ID of the Resource Owner model instance
|
166
253
|
#
|
167
254
|
# @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
|
168
255
|
# nil if nothing was found
|
169
256
|
#
|
170
|
-
def last_authorized_token_for(application_id,
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
257
|
+
def last_authorized_token_for(application_id, resource_owner)
|
258
|
+
authorized_tokens_for(application_id, resource_owner)
|
259
|
+
.ordered_by(:created_at, :desc)
|
260
|
+
.first
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Determines the secret storing transformer
|
265
|
+
# Unless configured otherwise, uses the plain secret strategy
|
266
|
+
#
|
267
|
+
# @return [Doorkeeper::SecretStoring::Base]
|
268
|
+
#
|
269
|
+
def secret_strategy
|
270
|
+
::Doorkeeper.config.token_secret_strategy
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# Determine the fallback storing strategy
|
275
|
+
# Unless configured, there will be no fallback
|
276
|
+
def fallback_secret_strategy
|
277
|
+
::Doorkeeper.config.token_secret_fallback_strategy
|
175
278
|
end
|
176
279
|
end
|
177
280
|
|
178
281
|
# Access Token type: Bearer.
|
179
|
-
# @see https://
|
282
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6750
|
180
283
|
# The OAuth 2.0 Authorization Framework: Bearer Token Usage
|
181
284
|
#
|
182
285
|
def token_type
|
183
|
-
|
286
|
+
"Bearer"
|
184
287
|
end
|
185
288
|
|
186
289
|
def use_refresh_token?
|
@@ -193,12 +296,16 @@ module Doorkeeper
|
|
193
296
|
# @return [Hash] hash with token data
|
194
297
|
def as_json(_options = {})
|
195
298
|
{
|
196
|
-
resource_owner_id:
|
197
|
-
|
198
|
-
|
199
|
-
application:
|
200
|
-
created_at:
|
201
|
-
}
|
299
|
+
resource_owner_id: resource_owner_id,
|
300
|
+
scope: scopes,
|
301
|
+
expires_in: expires_in_seconds,
|
302
|
+
application: { uid: application.try(:uid) },
|
303
|
+
created_at: created_at.to_i,
|
304
|
+
}.tap do |json|
|
305
|
+
if Doorkeeper.configuration.polymorphic_resource_owner?
|
306
|
+
json[:resource_owner_type] = resource_owner_type
|
307
|
+
end
|
308
|
+
end
|
202
309
|
end
|
203
310
|
|
204
311
|
# Indicates whether the token instance have the same credential
|
@@ -210,7 +317,22 @@ module Doorkeeper
|
|
210
317
|
#
|
211
318
|
def same_credential?(access_token)
|
212
319
|
application_id == access_token.application_id &&
|
320
|
+
same_resource_owner?(access_token)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Indicates whether the token instance have the same credential
|
324
|
+
# as the other Access Token.
|
325
|
+
#
|
326
|
+
# @param access_token [Doorkeeper::AccessToken] other token
|
327
|
+
#
|
328
|
+
# @return [Boolean] true if credentials are same of false in other cases
|
329
|
+
#
|
330
|
+
def same_resource_owner?(access_token)
|
331
|
+
if Doorkeeper.configuration.polymorphic_resource_owner?
|
332
|
+
resource_owner == access_token.resource_owner
|
333
|
+
else
|
213
334
|
resource_owner_id == access_token.resource_owner_id
|
335
|
+
end
|
214
336
|
end
|
215
337
|
|
216
338
|
# Indicates if token is acceptable for specific scopes.
|
@@ -224,18 +346,63 @@ module Doorkeeper
|
|
224
346
|
accessible? && includes_scope?(*scopes)
|
225
347
|
end
|
226
348
|
|
349
|
+
# We keep a volatile copy of the raw refresh token for initial communication
|
350
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
351
|
+
def plaintext_refresh_token
|
352
|
+
if secret_strategy.allows_restoring_secrets?
|
353
|
+
secret_strategy.restore_secret(self, :refresh_token)
|
354
|
+
else
|
355
|
+
@raw_refresh_token
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# We keep a volatile copy of the raw token for initial communication
|
360
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
361
|
+
#
|
362
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric encryption)
|
363
|
+
# while hashing strategies do not, so you cannot rely on this value
|
364
|
+
# returning a present value for persisted tokens.
|
365
|
+
def plaintext_token
|
366
|
+
if secret_strategy.allows_restoring_secrets?
|
367
|
+
secret_strategy.restore_secret(self, :token)
|
368
|
+
else
|
369
|
+
@raw_token
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
# Revokes token with `:refresh_token` equal to `:previous_refresh_token`
|
374
|
+
# and clears `:previous_refresh_token` attribute.
|
375
|
+
#
|
376
|
+
def revoke_previous_refresh_token!
|
377
|
+
return if !self.class.refresh_token_revoked_on_use? || previous_refresh_token.blank?
|
378
|
+
|
379
|
+
old_refresh_token&.revoke
|
380
|
+
update_attribute(:previous_refresh_token, "")
|
381
|
+
end
|
382
|
+
|
227
383
|
private
|
228
384
|
|
385
|
+
# Searches for Access Token record with `:refresh_token` equal to
|
386
|
+
# `:previous_refresh_token` value.
|
387
|
+
#
|
388
|
+
# @return [Doorkeeper::AccessToken, nil]
|
389
|
+
# Access Token record or nil if nothing found
|
390
|
+
#
|
391
|
+
def old_refresh_token
|
392
|
+
@old_refresh_token ||= self.class.by_previous_refresh_token(previous_refresh_token)
|
393
|
+
end
|
394
|
+
|
229
395
|
# Generates refresh token with UniqueToken generator.
|
230
396
|
#
|
231
397
|
# @return [String] refresh token value
|
232
398
|
#
|
233
399
|
def generate_refresh_token
|
234
|
-
|
400
|
+
@raw_refresh_token = UniqueToken.generate
|
401
|
+
secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token)
|
235
402
|
end
|
236
403
|
|
237
404
|
# Generates and sets the token value with the
|
238
|
-
# configured Generator class (see Doorkeeper.
|
405
|
+
# configured Generator class (see Doorkeeper.config).
|
239
406
|
#
|
240
407
|
# @return [String] generated token value
|
241
408
|
#
|
@@ -247,18 +414,39 @@ module Doorkeeper
|
|
247
414
|
def generate_token
|
248
415
|
self.created_at ||= Time.now.utc
|
249
416
|
|
250
|
-
|
251
|
-
self
|
417
|
+
@raw_token = token_generator.generate(attributes_for_token_generator)
|
418
|
+
secret_strategy.store_secret(self, :token, @raw_token)
|
419
|
+
@raw_token
|
420
|
+
end
|
421
|
+
|
422
|
+
# Set of attributes that would be passed to token generator to
|
423
|
+
# generate unique token based on them.
|
424
|
+
#
|
425
|
+
# @return [Hash] set of attributes
|
426
|
+
#
|
427
|
+
def attributes_for_token_generator
|
428
|
+
{
|
252
429
|
resource_owner_id: resource_owner_id,
|
253
430
|
scopes: scopes,
|
254
431
|
application: application,
|
255
432
|
expires_in: expires_in,
|
256
|
-
created_at: created_at
|
257
|
-
|
258
|
-
|
433
|
+
created_at: created_at,
|
434
|
+
}.tap do |attributes|
|
435
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
436
|
+
attributes[:resource_owner] = resource_owner
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def token_generator
|
442
|
+
generator_name = Doorkeeper.config.access_token_generator
|
443
|
+
generator = generator_name.constantize
|
444
|
+
|
445
|
+
return generator if generator.respond_to?(:generate)
|
446
|
+
|
259
447
|
raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
|
260
448
|
rescue NameError
|
261
|
-
raise Errors::TokenGeneratorNotFound, "#{
|
449
|
+
raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
|
262
450
|
end
|
263
451
|
end
|
264
452
|
end
|
@@ -1,34 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Doorkeeper
|
2
4
|
module ApplicationMixin
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
include OAuth::Helpers
|
8
|
+
include Models::Orderable
|
9
|
+
include Models::SecretStorable
|
6
10
|
include Models::Scopes
|
7
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
8
|
-
|
9
|
-
included do
|
10
|
-
has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
|
11
|
-
has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
|
12
|
-
|
13
|
-
validates :name, :secret, :uid, presence: true
|
14
|
-
validates :uid, uniqueness: true
|
15
|
-
validates :redirect_uri, redirect_uri: true
|
16
|
-
|
17
|
-
before_validation :generate_uid, :generate_secret, on: :create
|
18
|
-
end
|
19
11
|
|
12
|
+
# :nodoc
|
20
13
|
module ClassMethods
|
21
14
|
# Returns an instance of the Doorkeeper::Application with
|
22
15
|
# specific UID and secret.
|
23
16
|
#
|
17
|
+
# Public/Non-confidential applications will only find by uid if secret is
|
18
|
+
# blank.
|
19
|
+
#
|
24
20
|
# @param uid [#to_s] UID (any object that responds to `#to_s`)
|
25
21
|
# @param secret [#to_s] secret (any object that responds to `#to_s`)
|
26
22
|
#
|
27
|
-
# @return [Doorkeeper::Application, nil]
|
28
|
-
# if there is no record with such credentials
|
23
|
+
# @return [Doorkeeper::Application, nil]
|
24
|
+
# Application instance or nil if there is no record with such credentials
|
29
25
|
#
|
30
26
|
def by_uid_and_secret(uid, secret)
|
31
|
-
|
27
|
+
app = by_uid(uid)
|
28
|
+
return unless app
|
29
|
+
return app if secret.blank? && !app.confidential?
|
30
|
+
return unless app.secret_matches?(secret)
|
31
|
+
|
32
|
+
app
|
32
33
|
end
|
33
34
|
|
34
35
|
# Returns an instance of the Doorkeeper::Application with specific UID.
|
@@ -41,24 +42,54 @@ module Doorkeeper
|
|
41
42
|
def by_uid(uid)
|
42
43
|
find_by(uid: uid.to_s)
|
43
44
|
end
|
44
|
-
end
|
45
45
|
|
46
|
-
|
46
|
+
##
|
47
|
+
# Determines the secret storing transformer
|
48
|
+
# Unless configured otherwise, uses the plain secret strategy
|
49
|
+
def secret_strategy
|
50
|
+
::Doorkeeper.config.application_secret_strategy
|
51
|
+
end
|
47
52
|
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
##
|
54
|
+
# Determine the fallback storing strategy
|
55
|
+
# Unless configured, there will be no fallback
|
56
|
+
def fallback_secret_strategy
|
57
|
+
::Doorkeeper.config.application_secret_fallback_strategy
|
58
|
+
end
|
51
59
|
end
|
52
60
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
# Set an application's valid redirect URIs.
|
62
|
+
#
|
63
|
+
# @param uris [String, Array<String>] Newline-separated string or array the URI(s)
|
64
|
+
#
|
65
|
+
# @return [String] The redirect URI(s) separated by newlines.
|
66
|
+
#
|
67
|
+
def redirect_uri=(uris)
|
68
|
+
super(uris.is_a?(Array) ? uris.join("\n") : uris)
|
57
69
|
end
|
58
70
|
|
59
|
-
|
60
|
-
|
61
|
-
|
71
|
+
# Check whether the given plain text secret matches our stored secret
|
72
|
+
#
|
73
|
+
# @param input [#to_s] Plain secret provided by user
|
74
|
+
# (any object that responds to `#to_s`)
|
75
|
+
#
|
76
|
+
# @return [Boolean] Whether the given secret matches the stored secret
|
77
|
+
# of this application.
|
78
|
+
#
|
79
|
+
def secret_matches?(input)
|
80
|
+
# return false if either is nil, since secure_compare depends on strings
|
81
|
+
# but Application secrets MAY be nil depending on confidentiality.
|
82
|
+
return false if input.nil? || secret.nil?
|
83
|
+
|
84
|
+
# When matching the secret by comparer function, all is well.
|
85
|
+
return true if secret_strategy.secret_matches?(input, secret)
|
86
|
+
|
87
|
+
# When fallback lookup is enabled, ensure applications
|
88
|
+
# with plain secrets can still be found
|
89
|
+
if fallback_secret_strategy
|
90
|
+
fallback_secret_strategy.secret_matches?(input, secret)
|
91
|
+
else
|
92
|
+
false
|
62
93
|
end
|
63
94
|
end
|
64
95
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Doorkeeper
|
2
4
|
module Models
|
3
5
|
module Expirable
|
@@ -6,7 +8,7 @@ module Doorkeeper
|
|
6
8
|
#
|
7
9
|
# @return [Boolean] true if object expired and false in other case
|
8
10
|
def expired?
|
9
|
-
expires_in && Time.now.utc >
|
11
|
+
!!(expires_in && Time.now.utc > expires_at)
|
10
12
|
end
|
11
13
|
|
12
14
|
# Calculates expiration time in seconds.
|
@@ -15,15 +17,19 @@ module Doorkeeper
|
|
15
17
|
# or nil if object never expires.
|
16
18
|
def expires_in_seconds
|
17
19
|
return nil if expires_in.nil?
|
18
|
-
|
20
|
+
|
21
|
+
expires = expires_at - Time.now.utc
|
19
22
|
expires_sec = expires.seconds.round(0)
|
20
23
|
expires_sec > 0 ? expires_sec : 0
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
# Expiration time (date time of creation + TTL).
|
27
|
+
#
|
28
|
+
# @return [Time, nil] expiration time in UTC
|
29
|
+
# or nil if the object never expires.
|
30
|
+
#
|
31
|
+
def expires_at
|
32
|
+
expires_in && created_at + expires_in.seconds
|
27
33
|
end
|
28
34
|
end
|
29
35
|
end
|