doorkeeper-device_authorization_grant 0.1.1 → 0.2.0
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 +4 -4
- data/README.md +7 -0
- data/lib/doorkeeper/device_authorization_grant.rb +1 -0
- data/lib/doorkeeper/device_authorization_grant/config.rb +4 -2
- data/lib/doorkeeper/device_authorization_grant/oauth/device_authorization_request.rb +3 -2
- data/lib/doorkeeper/device_authorization_grant/oauth/device_authorization_response.rb +1 -0
- data/lib/doorkeeper/device_authorization_grant/oauth/device_code_request.rb +5 -4
- data/lib/doorkeeper/device_authorization_grant/orm/active_record/device_grant.rb +1 -116
- data/lib/doorkeeper/device_authorization_grant/orm/active_record/device_grant_mixin.rb +132 -0
- data/lib/doorkeeper/device_authorization_grant/version.rb +1 -1
- metadata +25 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '08caf432de1258cd6b0dab0346a332f0a49698e35838194e1e6e9f999d1a2ccc'
|
4
|
+
data.tar.gz: 98c6b042087cb1a1df1f84d4e9aca7327f2cb637eaee58729de53a0d76fd9900
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05dca92fad2e8ef88aae7d9739aa08c990dd5643c1ef4aab563b761d3afb1f08727efd2f508d45155e524943f9082203fa8e7019bd0829867a0c218266aa9fb8
|
7
|
+
data.tar.gz: c603c13d33a65616bd370dac83625396318ee7e47e0dc16f99cd620c23ebd5e1df0f9a30a6b7915eb33f85dd9ba7e2d2284793c0156808c86350591e97ae9085
|
data/README.md
CHANGED
@@ -316,5 +316,12 @@ Content-Type: application/json
|
|
316
316
|
The device authentication flow is now complete, and the token data can be used to
|
317
317
|
authenticate requests against the authorization and/or resource server.
|
318
318
|
|
319
|
+
## Example Application
|
320
|
+
|
321
|
+
Here you can find an example Rails application which uses this gem,
|
322
|
+
together with a little HTML/JS client to try out the device flow:
|
323
|
+
|
324
|
+
[https://github.com/exop-group/doorkeeper-device-flow-example](https://github.com/exop-group/doorkeeper-device-flow-example)
|
325
|
+
|
319
326
|
## License
|
320
327
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -9,6 +9,7 @@ module Doorkeeper
|
|
9
9
|
# OAuth 2.0 Device Authorization Grant extension for Doorkeeper.
|
10
10
|
module DeviceAuthorizationGrant
|
11
11
|
autoload :DeviceGrant, 'doorkeeper/device_authorization_grant/orm/active_record/device_grant'
|
12
|
+
autoload :DeviceGrantMixin, 'doorkeeper/device_authorization_grant/orm/active_record/device_grant_mixin'
|
12
13
|
autoload :Errors, 'doorkeeper/device_authorization_grant/errors'
|
13
14
|
autoload :VERSION, 'doorkeeper/device_authorization_grant/version'
|
14
15
|
|
@@ -18,8 +18,10 @@ module Doorkeeper
|
|
18
18
|
# Error raised in case of missing configuration
|
19
19
|
class MissingConfiguration < StandardError
|
20
20
|
def initialize
|
21
|
-
super(
|
22
|
-
|
21
|
+
super(
|
22
|
+
'Configuration for Doorkeeper::DeviceAuthorizationGrant missing. ' \
|
23
|
+
'Do you have Doorkeeper::DeviceAuthorizationGrant initializer?'
|
24
|
+
)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -7,8 +7,8 @@ module Doorkeeper
|
|
7
7
|
#
|
8
8
|
# @see https://tools.ietf.org/html/rfc8628#section-3.1 RFC 8628, sect. 3.1
|
9
9
|
class DeviceAuthorizationRequest < Doorkeeper::OAuth::BaseRequest
|
10
|
-
attr_accessor :server
|
11
|
-
|
10
|
+
attr_accessor :server,
|
11
|
+
:client
|
12
12
|
|
13
13
|
# @return [String]
|
14
14
|
attr_accessor :host_name
|
@@ -19,6 +19,7 @@ module Doorkeeper
|
|
19
19
|
# @param client
|
20
20
|
# @param host_name [String]
|
21
21
|
def initialize(server, client, host_name)
|
22
|
+
super()
|
22
23
|
@server = server
|
23
24
|
@client = client
|
24
25
|
@host_name = host_name
|
@@ -7,14 +7,13 @@ module Doorkeeper
|
|
7
7
|
#
|
8
8
|
# @see https://tools.ietf.org/html/rfc8628#section-3.4 RFC 8628, sect. 3.4
|
9
9
|
class DeviceCodeRequest < ::Doorkeeper::OAuth::BaseRequest
|
10
|
-
attr_accessor :server
|
11
|
-
|
10
|
+
attr_accessor :server,
|
11
|
+
:client,
|
12
|
+
:access_token
|
12
13
|
|
13
14
|
# @return [DeviceGrant]
|
14
15
|
attr_accessor :device_grant
|
15
16
|
|
16
|
-
attr_accessor :access_token
|
17
|
-
|
18
17
|
validate :client, error: :invalid_client
|
19
18
|
validate :device_grant, error: :invalid_grant
|
20
19
|
|
@@ -22,6 +21,8 @@ module Doorkeeper
|
|
22
21
|
# @param client
|
23
22
|
# @param device_grant [DeviceGrant]
|
24
23
|
def initialize(server, client, device_grant)
|
24
|
+
super()
|
25
|
+
|
25
26
|
@server = server
|
26
27
|
@client = client
|
27
28
|
@device_grant = device_grant
|
@@ -5,41 +5,7 @@ module Doorkeeper
|
|
5
5
|
# Model class, similar to Doorkeeper `AccessGrant`, but specific for
|
6
6
|
# handling OAuth 2.0 Device Authorization Grant.
|
7
7
|
class DeviceGrant < ActiveRecord::Base
|
8
|
-
|
9
|
-
|
10
|
-
include ::Doorkeeper::Models::Expirable
|
11
|
-
|
12
|
-
delegate :secret_strategy, :fallback_secret_strategy, to: :class
|
13
|
-
|
14
|
-
belongs_to :application, class_name: Doorkeeper.configuration.application_class, optional: true
|
15
|
-
|
16
|
-
before_validation :generate_device_code, on: :create
|
17
|
-
|
18
|
-
validates :application_id, presence: true
|
19
|
-
validates :expires_in, presence: true
|
20
|
-
validates :device_code, presence: true, uniqueness: true
|
21
|
-
|
22
|
-
validates :user_code, presence: true, uniqueness: true, if: -> { resource_owner_id.blank? }
|
23
|
-
validates :user_code, absence: true, if: -> { resource_owner_id.present? }
|
24
|
-
|
25
|
-
validates :resource_owner_id, presence: true, if: -> { user_code.blank? }
|
26
|
-
validates :resource_owner_id, absence: true, if: -> { user_code.present? }
|
27
|
-
|
28
|
-
scope(
|
29
|
-
:expired,
|
30
|
-
lambda do
|
31
|
-
exp_in = DeviceAuthorizationGrant.configuration.device_code_expires_in
|
32
|
-
where('created_at <= :expiration_date', expiration_date: exp_in.seconds.ago)
|
33
|
-
end
|
34
|
-
)
|
35
|
-
|
36
|
-
scope(
|
37
|
-
:unexpired,
|
38
|
-
lambda do
|
39
|
-
exp_in = DeviceAuthorizationGrant.configuration.device_code_expires_in
|
40
|
-
where('created_at > :expiration_date', expiration_date: exp_in.seconds.ago)
|
41
|
-
end
|
42
|
-
)
|
8
|
+
include DeviceGrantMixin
|
43
9
|
|
44
10
|
# @!attribute application_id
|
45
11
|
# @return [Integer]
|
@@ -64,87 +30,6 @@ module Doorkeeper
|
|
64
30
|
|
65
31
|
# @!attribute last_polling_at
|
66
32
|
# @return [Time, nil]
|
67
|
-
|
68
|
-
class << self
|
69
|
-
# Returns an instance of the DeviceGrant with specific device code
|
70
|
-
# value.
|
71
|
-
#
|
72
|
-
# @param device_code [#to_s] device code value
|
73
|
-
# @return [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant, nil]
|
74
|
-
# DeviceGrant object, or nil if there is no record with such code
|
75
|
-
def find_by_plaintext_device_code(device_code)
|
76
|
-
device_code = device_code.to_s
|
77
|
-
|
78
|
-
find_by(device_code: secret_strategy.transform_secret(device_code)) ||
|
79
|
-
find_by_fallback_device_code(device_code)
|
80
|
-
end
|
81
|
-
|
82
|
-
alias by_device_code find_by_plaintext_device_code
|
83
|
-
|
84
|
-
# Allow looking up previously plain device codes as a fallback IFF a
|
85
|
-
# fallback strategy has been defined
|
86
|
-
#
|
87
|
-
# @param plain_secret [#to_s] plain secret value
|
88
|
-
# @return [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant, nil]
|
89
|
-
# DeviceGrant object or nil if there is no record with such code
|
90
|
-
def find_by_fallback_device_code(plain_secret)
|
91
|
-
return nil unless fallback_secret_strategy
|
92
|
-
|
93
|
-
# Use the previous strategy to look up
|
94
|
-
stored_code = fallback_secret_strategy.transform_secret(plain_secret)
|
95
|
-
find_by(device_code: stored_code).tap do |resource|
|
96
|
-
upgrade_fallback_value(resource, plain_secret) if resource
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Allows to replace a plain value fallback, to avoid it remaining as
|
101
|
-
# plain text.
|
102
|
-
#
|
103
|
-
# @param instance [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant]
|
104
|
-
# An instance of this model with a plain value device code.
|
105
|
-
# @param plain_secret [String] The plain secret to upgrade.
|
106
|
-
def upgrade_fallback_value(instance, plain_secret)
|
107
|
-
upgraded =
|
108
|
-
secret_strategy.store_secret(instance, :device_code, plain_secret)
|
109
|
-
instance.update(device_code: upgraded)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Determines the secret storing transformer
|
113
|
-
# Unless configured otherwise, uses the plain secret strategy
|
114
|
-
def secret_strategy
|
115
|
-
::Doorkeeper.configuration.token_secret_strategy
|
116
|
-
end
|
117
|
-
|
118
|
-
# Determine the fallback storing strategy
|
119
|
-
# Unless configured, there will be no fallback
|
120
|
-
def fallback_secret_strategy
|
121
|
-
::Doorkeeper.configuration.token_secret_fallback_strategy
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# We keep a volatile copy of the raw device code for initial
|
126
|
-
# communication.
|
127
|
-
#
|
128
|
-
# Some strategies allow restoring stored secrets (e.g. symmetric
|
129
|
-
# encryption) while hashing strategies do not, so you cannot rely on
|
130
|
-
# this value returning a present value for persisted device codes.
|
131
|
-
def plaintext_device_code
|
132
|
-
if secret_strategy.allows_restoring_secrets?
|
133
|
-
secret_strategy.restore_secret(self, :device_code)
|
134
|
-
else
|
135
|
-
@raw_device_code
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
# Generates a device code value with UniqueToken class.
|
142
|
-
#
|
143
|
-
# @return [String] device code value
|
144
|
-
def generate_device_code
|
145
|
-
@raw_device_code = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
146
|
-
secret_strategy.store_secret(self, :device_code, @raw_device_code)
|
147
|
-
end
|
148
33
|
end
|
149
34
|
end
|
150
35
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module DeviceAuthorizationGrant
|
5
|
+
# Module mixin for Device Grant models.
|
6
|
+
#
|
7
|
+
# This is similar to Doorkeeper `AccessGrantMixin`, but specific for handling
|
8
|
+
# OAuth 2.0 Device Authorization Grant.
|
9
|
+
module DeviceGrantMixin
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
include ::Doorkeeper::Models::Expirable
|
12
|
+
|
13
|
+
included do
|
14
|
+
self.table_name = "#{table_name_prefix}oauth_device_grants#{table_name_suffix}"
|
15
|
+
|
16
|
+
delegate :secret_strategy, :fallback_secret_strategy, to: :class
|
17
|
+
|
18
|
+
belongs_to :application, class_name: Doorkeeper.configuration.application_class, optional: true
|
19
|
+
|
20
|
+
before_validation :generate_device_code, on: :create
|
21
|
+
|
22
|
+
validates :application_id, presence: true
|
23
|
+
validates :expires_in, presence: true
|
24
|
+
validates :device_code, presence: true, uniqueness: true
|
25
|
+
|
26
|
+
validates :user_code, presence: true, uniqueness: true, if: -> { resource_owner_id.blank? }
|
27
|
+
validates :user_code, absence: true, if: -> { resource_owner_id.present? }
|
28
|
+
|
29
|
+
validates :resource_owner_id, presence: true, if: -> { user_code.blank? }
|
30
|
+
validates :resource_owner_id, absence: true, if: -> { user_code.present? }
|
31
|
+
|
32
|
+
scope(
|
33
|
+
:expired,
|
34
|
+
lambda do
|
35
|
+
exp_in = DeviceAuthorizationGrant.configuration.device_code_expires_in
|
36
|
+
where('created_at <= :expiration_date', expiration_date: exp_in.seconds.ago)
|
37
|
+
end
|
38
|
+
)
|
39
|
+
|
40
|
+
scope(
|
41
|
+
:unexpired,
|
42
|
+
lambda do
|
43
|
+
exp_in = DeviceAuthorizationGrant.configuration.device_code_expires_in
|
44
|
+
where('created_at > :expiration_date', expiration_date: exp_in.seconds.ago)
|
45
|
+
end
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
# ClassMethods
|
50
|
+
module ClassMethods
|
51
|
+
# Returns an instance of the DeviceGrant with specific device code
|
52
|
+
# value.
|
53
|
+
#
|
54
|
+
# @param device_code [#to_s] device code value
|
55
|
+
# @return [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant, nil]
|
56
|
+
# DeviceGrant object, or nil if there is no record with such code
|
57
|
+
def find_by_plaintext_device_code(device_code)
|
58
|
+
device_code = device_code.to_s
|
59
|
+
|
60
|
+
find_by(device_code: secret_strategy.transform_secret(device_code)) ||
|
61
|
+
find_by_fallback_device_code(device_code)
|
62
|
+
end
|
63
|
+
|
64
|
+
alias by_device_code find_by_plaintext_device_code
|
65
|
+
|
66
|
+
# Allow looking up previously plain device codes as a fallback IFF a
|
67
|
+
# fallback strategy has been defined
|
68
|
+
#
|
69
|
+
# @param plain_secret [#to_s] plain secret value
|
70
|
+
# @return [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant, nil]
|
71
|
+
# DeviceGrant object or nil if there is no record with such code
|
72
|
+
def find_by_fallback_device_code(plain_secret)
|
73
|
+
return nil unless fallback_secret_strategy
|
74
|
+
|
75
|
+
# Use the previous strategy to look up
|
76
|
+
stored_code = fallback_secret_strategy.transform_secret(plain_secret)
|
77
|
+
find_by(device_code: stored_code).tap do |resource|
|
78
|
+
upgrade_fallback_value(resource, plain_secret) if resource
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Allows to replace a plain value fallback, to avoid it remaining as
|
83
|
+
# plain text.
|
84
|
+
#
|
85
|
+
# @param instance [Doorkeeper::DeviceAuthorizationGrant::DeviceGrant]
|
86
|
+
# An instance of this model with a plain value device code.
|
87
|
+
# @param plain_secret [String] The plain secret to upgrade.
|
88
|
+
def upgrade_fallback_value(instance, plain_secret)
|
89
|
+
upgraded =
|
90
|
+
secret_strategy.store_secret(instance, :device_code, plain_secret)
|
91
|
+
instance.update(device_code: upgraded)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Determines the secret storing transformer
|
95
|
+
# Unless configured otherwise, uses the plain secret strategy
|
96
|
+
def secret_strategy
|
97
|
+
::Doorkeeper.configuration.token_secret_strategy
|
98
|
+
end
|
99
|
+
|
100
|
+
# Determine the fallback storing strategy
|
101
|
+
# Unless configured, there will be no fallback
|
102
|
+
def fallback_secret_strategy
|
103
|
+
::Doorkeeper.configuration.token_secret_fallback_strategy
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# We keep a volatile copy of the raw device code for initial
|
108
|
+
# communication.
|
109
|
+
#
|
110
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric
|
111
|
+
# encryption) while hashing strategies do not, so you cannot rely on
|
112
|
+
# this value returning a present value for persisted device codes.
|
113
|
+
def plaintext_device_code
|
114
|
+
if secret_strategy.allows_restoring_secrets?
|
115
|
+
secret_strategy.restore_secret(self, :device_code)
|
116
|
+
else
|
117
|
+
@raw_device_code
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Generates a device code value with UniqueToken class.
|
124
|
+
#
|
125
|
+
# @return [String] device code value
|
126
|
+
def generate_device_code
|
127
|
+
@raw_device_code = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
128
|
+
secret_strategy.store_secret(self, :device_code, @raw_device_code)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doorkeeper-device_authorization_grant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- EXOP Group
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: doorkeeper
|
@@ -30,56 +30,70 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '1.8'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '1.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.9.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.9.1
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: simplecov
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
61
|
+
version: 0.21.1
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
68
|
+
version: 0.21.1
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: sqlite3
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
75
|
+
version: 1.4.2
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
82
|
+
version: 1.4.2
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: yard
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.9.
|
89
|
+
version: 0.9.26
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.9.
|
96
|
+
version: 0.9.26
|
83
97
|
description: OAuth 2.0 Device Authorization Grant extension for Doorkeeper.
|
84
98
|
email:
|
85
99
|
- opensource@exop-group.com
|
@@ -105,6 +119,7 @@ files:
|
|
105
119
|
- lib/doorkeeper/device_authorization_grant/oauth/helpers/user_code.rb
|
106
120
|
- lib/doorkeeper/device_authorization_grant/orm/active_record.rb
|
107
121
|
- lib/doorkeeper/device_authorization_grant/orm/active_record/device_grant.rb
|
122
|
+
- lib/doorkeeper/device_authorization_grant/orm/active_record/device_grant_mixin.rb
|
108
123
|
- lib/doorkeeper/device_authorization_grant/rails/routes.rb
|
109
124
|
- lib/doorkeeper/device_authorization_grant/rails/routes/mapper.rb
|
110
125
|
- lib/doorkeeper/device_authorization_grant/rails/routes/mapping.rb
|