doorkeeper-jwt_assertion 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27fd53398a97014fdf2e15738c0a3e8b614fe9c9
4
- data.tar.gz: 122d76d1dfee424e13f85983aaf6e3bbfb69b142
3
+ metadata.gz: b1025f43737a9664dcc50c45898f896b533d3c63
4
+ data.tar.gz: f3b91579bb961c9d970d09016397bbb5e951faa1
5
5
  SHA512:
6
- metadata.gz: 1f58e5c6f1ca803e953c95567729a1aba332173560c27a9c339426f2346603430c60e78f17d3088a8a650e7a79a5db1f893319f66dce708a5a93417a713d0623
7
- data.tar.gz: 57f040de3c1bd245eb7ea76db943003320eade8a2dc8126e3c379def4b499c74c6e5b53417919fa857313d59931c7040ae89ea819a41eb4cf6e74bf3da2237ea
6
+ metadata.gz: 0a275595653379460375328fe8c5d43802d15ac096c48c2e07ad848f23f4063f50989cb0e386c235508a2182c8e8a0c139754ea4a25eeb8ae93481368852f893
7
+ data.tar.gz: 6ce0dbc0869904a8df606fbcc95d68a81c91548cca8b3c62df20c9f18edfdd61c6e8f382e701a1d221ff84918b1afc2ad948bf24c4e145e93ab44e8cc66ae318
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Doorkeeper JWT Assertion
2
2
 
3
- ## Description
4
-
5
- Extending (Doorkeeper)[https://github.com/doorkeeper-gem/doorkeeper] to support JWT Assertion grant type using a secret or a private key file.
3
+ Extending [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) to support JWT Assertion grant type using a secret or a private key file.
6
4
 
7
5
  **This library is in alpha. Future incompatible changes may be necessary.**
8
6
 
@@ -24,13 +22,18 @@ Doorkeeper.configure do
24
22
  jwt_private_key Rails.root.join('config', 'keys', 'private.key')
25
23
 
26
24
  jwt_secret 'notasecret'
25
+
26
+ # Optional
27
+ jwt_use_issuer_as_client_id true
27
28
  end
28
29
  ```
29
30
 
30
31
  This will automatically push `assertion` into the Doorkeeper's grant_types configuration attribute.
31
32
 
32
- You can also use the `resource_owner_authenticator` in the configuration to identify the owner based on the JWT claim values.
33
- If the client request a token with an invalid assertion, an error will be raised. So you can rely on the `jwt` getter if an assertion grant was requested.
33
+ When `jwt_use_issuer_as_client_id` is set to false then the `client_id` MUST be available from the parameters. By default it will extract the 'iss' and use it as the client_id to retrieve the oauth application.
34
+
35
+ Use the `resource_owner_authenticator` in the configuration to identify the owner based on the JWT claim values. This values can be accessible from `jwt`.
36
+ If the client request a token with an invalid assertion, or an expired JWT claim, an :invalid_grant error response will be generated before retrieving the resource_owner.
34
37
 
35
38
  ``` ruby
36
39
  Doorkeeper.configure do
@@ -38,8 +41,7 @@ Doorkeeper.configure do
38
41
  resource_owner_authenticator do
39
42
 
40
43
  if jwt
41
- head :unauthorized unless user = User.where(:email => jwt['prn']).first
42
- return user
44
+ jwt['prn'].present? and User.find_by_email(jwt['prn'])
43
45
  end
44
46
 
45
47
  end
@@ -60,14 +62,20 @@ p12 = OpenSSL::PKCS12.new( Rails.root.join('config', 'keys', 'private.p12').open
60
62
  params = { :private_key => p12.key,
61
63
  :aud => 'audience',
62
64
  :prn => 'person', # or :sub => 'subject', not suported on OAuth2 1.0.0 yet.
63
- :iss => 'issuer',
65
+ :iss => 'client_id',
64
66
  :scope => 'scope',
65
67
  :exp => Time.now.utc.to_i + 5.minutes }
66
68
 
67
69
  token = client.assertion.get_token(params)
68
70
  ```
69
71
 
72
+ > "[...] refresh tokens are not issued
73
+ > in response to assertion grant requests and access tokens will be
74
+ > issued with a reasonably short lifetime."
75
+ > - [draft-ietf-oauth-assertions-18](https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1)
76
+
70
77
  ## TO DO
71
78
 
72
79
  * Better error handling
80
+ * JWT Client Authentication Flow
73
81
  * Testing
@@ -7,7 +7,7 @@ require 'jwt'
7
7
  module Doorkeeper
8
8
  module JWTAssertion
9
9
 
10
- attr_reader :jwt
10
+ attr_reader :jwt, :jwt_header
11
11
 
12
12
  end
13
13
  end
@@ -22,6 +22,10 @@ module Doorkeeper
22
22
  context.instance_variable_set('@jwt', jwt)
23
23
  end
24
24
 
25
+ def jwt_header=(jwt_header)
26
+ @jwt_header = jwt_header
27
+ context.instance_variable_set('@jwt_header', jwt_header)
28
+ end
25
29
 
26
30
  end
27
31
  end
@@ -30,6 +34,7 @@ module Doorkeeper
30
34
  class Config
31
35
 
32
36
  option :jwt_key
37
+ option :jwt_use_issuer_as_client_id, :default => true
33
38
 
34
39
  class Builder
35
40
 
@@ -48,10 +53,10 @@ module Doorkeeper
48
53
 
49
54
  Config.class_eval do
50
55
  alias_method :remember_calculate_token_grant_types, :calculate_token_grant_types
51
-
52
56
  define_method :calculate_token_grant_types do
53
- remember_calculate_token_grant_types << 'assertion'
57
+ remember_calculate_token_grant_types << 'assertion' << 'urn:ietf:params:oauth:grant-type:jwt-bearer'
54
58
  end
59
+
55
60
  end
56
61
 
57
62
  jwt_key key
@@ -1,5 +1,5 @@
1
1
  module Doorkeeper
2
2
  module JwtAssertion
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -0,0 +1,149 @@
1
+ require 'doorkeeper/oauth/describable_error_response'
2
+
3
+ module Doorkeeper
4
+ module OAuth
5
+ class AssertionAccessTokenRequest
6
+
7
+ include Validations
8
+ include OAuth::RequestConcern
9
+ include OAuth::Helpers
10
+
11
+ attr_accessor :server, :original_scopes
12
+ attr_reader :resource_owner, :client, :configuration, :access_token, :response, :error_description
13
+
14
+ validate :assertion, error: :invalid_grant
15
+ validate :client, error: :invalid_client
16
+ validate :scopes, error: :invalid_scope
17
+ validate :resource_owner, error: :invalid_grant
18
+ validate :access_token, error: :invalid_grant
19
+
20
+
21
+ ##
22
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
23
+ #
24
+ def initialize(server, configuration)
25
+ @server = server
26
+ @configuration = configuration
27
+ @response = nil
28
+ @original_scopes = server.parameters[:scope]
29
+ end
30
+
31
+ def authorize
32
+ validate
33
+ if valid?
34
+ @response = TokenResponse.new(access_token)
35
+ else
36
+ @response = DescribableErrorResponse.from_request(self)
37
+ @response.description = error_description
38
+ @response
39
+ end
40
+ end
41
+
42
+
43
+
44
+ private
45
+
46
+ ##
47
+ # > The value of the "grant_type" is "urn:ietf:params:oauth:grant-
48
+ # > type:jwt-bearer".
49
+ # > The value of the "assertion" parameter MUST contain a single JWT.
50
+ # > - draft-ietf-oauth-jwt-bearer-12
51
+ #
52
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
53
+ #
54
+ # > assertion_type
55
+ # > REQUIRED. The format of the assertion as defined by the
56
+ # > authorization server. The value MUST be an absolute URI.
57
+ # >
58
+ # > assertion
59
+ # > REQUIRED. The assertion.
60
+ # >
61
+ # > - draft-ietf-oauth-v2-10
62
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
63
+ #
64
+ # Newer versions of ietf-oauth-v2 don't need assertion_type. So it's still optional.
65
+ def validate_assertion
66
+ assertion, assertion_type = server.parameters.values_at(:assertion, :assertion_type)
67
+
68
+ if assertion_type and assertion_type != 'urn:ietf:params:oauth:grant-type:jwt-bearer'
69
+ raise StandardError.new('Assertion type not valid. Expected urn:ietf:params:oauth:grant-type:jwt-bearer')
70
+ end
71
+
72
+ payload, header = JWT.decode(assertion, configuration.jwt_key)
73
+ server.jwt = payload
74
+ server.jwt_header = header
75
+
76
+ rescue => error
77
+ @error_description = error.message
78
+ false
79
+ end
80
+
81
+
82
+
83
+ ##
84
+ # If `jwt_use_issuer_as_client_id` is `true` then validate the client using the issuer as the client_id.
85
+ # Otherwise, use the client_id directly from the parameters.
86
+ #
87
+ # > Authentication of the client is optional, as described in
88
+ # > Section 3.2.1 of OAuth 2.0 [RFC6749] and consequently, the
89
+ # > "client_id" is only needed when a form of client authentication that
90
+ # > relies on the parameter is used.
91
+ # > - draft-ietf-oauth-assertions-18
92
+ #
93
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1
94
+ #
95
+ # > The JWT MUST contain an "iss" (issuer) claim that contains a
96
+ # > unique identifier for the entity that issued the JWT.
97
+ # > - draft-ietf-oauth-jwt-bearer-12
98
+ #
99
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-3
100
+ #
101
+ def validate_client
102
+ @client ||= if configuration.jwt_use_issuer_as_client_id
103
+ OAuth::Client.find(server.jwt['iss']) if server.jwt['iss'].present?
104
+
105
+ elsif sever.parameters[:client_id].present?
106
+ OAuth::Client.find( sever.parameters[:client_id] )
107
+
108
+ end
109
+ end
110
+
111
+
112
+ ##
113
+ # > The "scope" parameter may be used, as defined in the Assertion
114
+ # > Framework for OAuth 2.0 Client Authentication and Authorization
115
+ # > Grants [I-D.ietf-oauth-assertions] specification, to indicate the
116
+ # > requested scope.
117
+ #
118
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12#section-2.1
119
+ #
120
+ def validate_scopes
121
+ return true unless @original_scopes.present?
122
+ ScopeChecker.valid? @original_scopes, configuration.scopes, client.try(:scopes)
123
+ end
124
+
125
+
126
+ def validate_resource_owner
127
+ resource_owner || (@resource_owner = server.current_resource_owner)
128
+ end
129
+
130
+ def validate_access_token
131
+ access_token or create_token
132
+ end
133
+
134
+
135
+ ##
136
+ # > [...] refresh tokens are not issued
137
+ # > in response to assertion grant requests and access tokens will be
138
+ # > issued with a reasonably short lifetime.
139
+ #
140
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-assertions-18#section-4.1
141
+ #
142
+ def create_token
143
+ expires_in = Authorization::Token.access_token_expires_in(configuration, client)
144
+ @access_token = AccessToken.find_or_create_for(client, resource_owner.id, scopes, expires_in, false)
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,8 @@
1
+ module Doorkeeper
2
+ module OAuth
3
+
4
+ class DescribableErrorResponse < ErrorResponse
5
+ attr_accessor :description
6
+ end
7
+ end
8
+ end
@@ -1,38 +1,27 @@
1
+ require 'doorkeeper/oauth/assertion_access_token_request'
2
+
1
3
  module Doorkeeper
2
4
  module Request
3
5
 
4
6
  class Assertion
5
7
  def self.build(server)
6
- assertion = server.parameters[:assertion]
7
- begin
8
- jwt = JWT.decode(assertion, Doorkeeper.configuration.jwt_key)
9
- rescue JWT::ExpiredSignature => e
10
- raise Errors::ExpiredSignature
11
- end
12
- server.jwt = jwt.is_a?(Array) ? jwt.first : jwt
13
-
14
- new(server.credentials, server.current_resource_owner, server)
8
+ new(server)
15
9
  end
16
10
 
17
- attr_accessor :credentials, :resource_owner, :server
11
+ attr_reader :server
18
12
 
19
- def initialize(credentials, resource_owner, server)
20
- @credentials = credentials
21
- @resource_owner = resource_owner
13
+ def initialize(server)
22
14
  @server = server
23
15
  end
24
16
 
25
17
  def request
26
- @request ||= OAuth::PasswordAccessTokenRequest.new(
27
- Doorkeeper.configuration,
28
- credentials,
29
- resource_owner,
30
- server.parameters)
18
+ @request ||= OAuth::AssertionAccessTokenRequest.new(server, Doorkeeper.configuration)
31
19
  end
32
20
 
33
21
  def authorize
34
22
  request.authorize
35
23
  end
24
+
36
25
  end
37
26
 
38
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper-jwt_assertion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Omac
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-27 00:00:00.000000000 Z
11
+ date: 2015-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: doorkeeper
@@ -83,6 +83,8 @@ files:
83
83
  - lib/doorkeeper/jwt_assertion.rb
84
84
  - lib/doorkeeper/jwt_assertion/railtie.rb
85
85
  - lib/doorkeeper/jwt_assertion/version.rb
86
+ - lib/doorkeeper/oauth/assertion_access_token_request.rb
87
+ - lib/doorkeeper/oauth/describable_error_response.rb
86
88
  - lib/doorkeeper/request/assertion.rb
87
89
  homepage: https://github.com/kioru/doorkeeper-jwt_assertion
88
90
  licenses: