adal 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +7 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +25 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +97 -0
  8. data/Rakefile +39 -0
  9. data/adal.gemspec +52 -0
  10. data/contributing.md +127 -0
  11. data/lib/adal.rb +24 -0
  12. data/lib/adal/authentication_context.rb +202 -0
  13. data/lib/adal/authentication_parameters.rb +126 -0
  14. data/lib/adal/authority.rb +165 -0
  15. data/lib/adal/cache_driver.rb +171 -0
  16. data/lib/adal/cached_token_response.rb +190 -0
  17. data/lib/adal/client_assertion.rb +63 -0
  18. data/lib/adal/client_assertion_certificate.rb +89 -0
  19. data/lib/adal/client_credential.rb +46 -0
  20. data/lib/adal/core_ext.rb +26 -0
  21. data/lib/adal/core_ext/hash.rb +34 -0
  22. data/lib/adal/jwt_parameters.rb +39 -0
  23. data/lib/adal/logger.rb +90 -0
  24. data/lib/adal/logging.rb +98 -0
  25. data/lib/adal/memory_cache.rb +95 -0
  26. data/lib/adal/mex_request.rb +52 -0
  27. data/lib/adal/mex_response.rb +141 -0
  28. data/lib/adal/noop_cache.rb +38 -0
  29. data/lib/adal/oauth_request.rb +76 -0
  30. data/lib/adal/request_parameters.rb +48 -0
  31. data/lib/adal/self_signed_jwt_factory.rb +96 -0
  32. data/lib/adal/templates/rst.13.xml.erb +35 -0
  33. data/lib/adal/templates/rst.2005.xml.erb +32 -0
  34. data/lib/adal/token_request.rb +231 -0
  35. data/lib/adal/token_response.rb +144 -0
  36. data/lib/adal/user_assertion.rb +57 -0
  37. data/lib/adal/user_credential.rb +152 -0
  38. data/lib/adal/user_identifier.rb +83 -0
  39. data/lib/adal/user_information.rb +49 -0
  40. data/lib/adal/util.rb +49 -0
  41. data/lib/adal/version.rb +36 -0
  42. data/lib/adal/wstrust_request.rb +100 -0
  43. data/lib/adal/wstrust_response.rb +168 -0
  44. data/lib/adal/xml_namespaces.rb +64 -0
  45. data/samples/authorization_code_example/README.md +10 -0
  46. data/samples/authorization_code_example/web_app.rb +139 -0
  47. data/samples/client_assertion_certificate_example/README.md +42 -0
  48. data/samples/client_assertion_certificate_example/app.rb +55 -0
  49. data/samples/on_behalf_of_example/README.md +35 -0
  50. data/samples/on_behalf_of_example/native_app.rb +52 -0
  51. data/samples/on_behalf_of_example/web_api.rb +71 -0
  52. data/samples/user_credentials_example/README.md +7 -0
  53. data/samples/user_credentials_example/app.rb +52 -0
  54. data/spec/adal/authentication_context_spec.rb +186 -0
  55. data/spec/adal/authentication_parameters_spec.rb +107 -0
  56. data/spec/adal/authority_spec.rb +122 -0
  57. data/spec/adal/cache_driver_spec.rb +191 -0
  58. data/spec/adal/cached_token_response_spec.rb +148 -0
  59. data/spec/adal/client_assertion_certificate_spec.rb +113 -0
  60. data/spec/adal/client_assertion_spec.rb +38 -0
  61. data/spec/adal/core_ext/hash_spec.rb +47 -0
  62. data/spec/adal/logging_spec.rb +48 -0
  63. data/spec/adal/memory_cache_spec.rb +107 -0
  64. data/spec/adal/mex_request_spec.rb +57 -0
  65. data/spec/adal/mex_response_spec.rb +143 -0
  66. data/spec/adal/self_signed_jwt_factory_spec.rb +63 -0
  67. data/spec/adal/token_request_spec.rb +150 -0
  68. data/spec/adal/token_response_spec.rb +102 -0
  69. data/spec/adal/user_credential_spec.rb +125 -0
  70. data/spec/adal/user_identifier_spec.rb +115 -0
  71. data/spec/adal/wstrust_request_spec.rb +51 -0
  72. data/spec/adal/wstrust_response_spec.rb +152 -0
  73. data/spec/fixtures/mex/insecureaddress.xml +924 -0
  74. data/spec/fixtures/mex/invalid_namespaces.xml +916 -0
  75. data/spec/fixtures/mex/malformed.xml +914 -0
  76. data/spec/fixtures/mex/microsoft.xml +916 -0
  77. data/spec/fixtures/mex/multiple_endpoints.xml +922 -0
  78. data/spec/fixtures/mex/no_matching_bindings.xml +916 -0
  79. data/spec/fixtures/mex/no_username_token_policies.xml +914 -0
  80. data/spec/fixtures/mex/no_wstrust_endpoints.xml +838 -0
  81. data/spec/fixtures/mex/only_13.xml +842 -0
  82. data/spec/fixtures/mex/only_2005.xml +842 -0
  83. data/spec/fixtures/oauth/error.json +1 -0
  84. data/spec/fixtures/oauth/success.json +1 -0
  85. data/spec/fixtures/oauth/success_with_id_token.json +1 -0
  86. data/spec/fixtures/wstrust/error.xml +24 -0
  87. data/spec/fixtures/wstrust/invalid_namespaces.xml +136 -0
  88. data/spec/fixtures/wstrust/missing_security_tokens.xml +90 -0
  89. data/spec/fixtures/wstrust/success.xml +136 -0
  90. data/spec/fixtures/wstrust/token.xml +1 -0
  91. data/spec/fixtures/wstrust/too_many_security_tokens.xml +219 -0
  92. data/spec/fixtures/wstrust/unrecognized_token_type.xml +136 -0
  93. data/spec/fixtures/wstrust/wstrust.13.xml +1 -0
  94. data/spec/fixtures/wstrust/wstrust.2005.xml +89 -0
  95. data/spec/spec_helper.rb +53 -0
  96. data/spec/support/fake_data.rb +40 -0
  97. data/spec/support/fake_token_endpoint.rb +108 -0
  98. metadata +265 -0
@@ -0,0 +1,38 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2015 Micorosft Corporation
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #-------------------------------------------------------------------------------
22
+
23
+ module ADAL
24
+ # A cache implementation that holds no values and ignores all method calls.
25
+ class NoopCache
26
+ # Swallows any number of parameters and returns nil.
27
+ def noop(*); end
28
+
29
+ alias_method :add, :noop
30
+ alias_method :add_many, :noop
31
+ alias_method :remove, :noop
32
+ alias_method :remove_many, :noop
33
+
34
+ def find(*)
35
+ []
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,76 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2015 Micorosft Corporation
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #-------------------------------------------------------------------------------
22
+
23
+ require_relative './logging'
24
+ require_relative './request_parameters'
25
+ require_relative './util'
26
+
27
+ require 'net/http'
28
+ require 'uri'
29
+
30
+ module ADAL
31
+ # A request that can be made to an authentication or token server.
32
+ class OAuthRequest
33
+ include RequestParameters
34
+ include Util
35
+
36
+ DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
37
+ DEFAULT_ENCODING = 'utf8'
38
+ SSL_SCHEME = 'https'
39
+
40
+ def initialize(endpoint, params)
41
+ @endpoint_uri = URI.parse(endpoint.to_s)
42
+ @params = params
43
+ end
44
+
45
+ def params
46
+ default_parameters.merge(@params)
47
+ end
48
+
49
+ ##
50
+ # Requests and waits for a token from the endpoint.
51
+ # @return TokenResponse
52
+ def execute
53
+ request = Net::HTTP::Post.new(@endpoint_uri.path)
54
+ add_headers(request)
55
+ request.body = URI.encode_www_form(string_hash(params))
56
+ TokenResponse.parse(http(@endpoint_uri).request(request).body)
57
+ end
58
+
59
+ private
60
+
61
+ ##
62
+ # Adds the necessary OAuth headers.
63
+ #
64
+ # @param Net::HTTPGenericRequest
65
+ def add_headers(request)
66
+ return if Logging.correlation_id.nil?
67
+ request.add_field(CLIENT_REQUEST_ID.to_s, Logging.correlation_id)
68
+ request.add_field(CLIENT_RETURN_CLIENT_REQUEST_ID.to_s, true)
69
+ end
70
+
71
+ def default_parameters
72
+ { encoding: DEFAULT_ENCODING,
73
+ AAD_API_VERSION => '1.0' }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2015 Micorosft Corporation
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #-------------------------------------------------------------------------------
22
+
23
+ module ADAL
24
+ # Names of parameters in OAuth requests. This module can be included in any
25
+ # class to reference the parameters instead of referring to them as strings
26
+ # or symbols
27
+ module RequestParameters
28
+ AAD_API_VERSION = 'api-version'.to_sym
29
+ ASSERTION = 'assertion'.to_sym
30
+ CLIENT_ASSERTION = 'client_assertion'.to_sym
31
+ CLIENT_ASSERTION_TYPE = 'client_assertion_type'.to_sym
32
+ CLIENT_ID = 'client_id'.to_sym
33
+ CLIENT_REQUEST_ID = 'client-request-id'.to_sym
34
+ CLIENT_RETURN_CLIENT_REQUEST_ID = 'client-return-client-request-id'.to_sym
35
+ CLIENT_SECRET = 'client_secret'.to_sym
36
+ CODE = 'code'.to_sym
37
+ FORM_POST = 'form_post'.to_sym
38
+ GRANT_TYPE = 'grant_type'.to_sym
39
+ PASSWORD = 'password'.to_sym
40
+ REDIRECT_URI = 'redirect_uri'.to_sym
41
+ REFRESH_TOKEN = 'refresh_token'.to_sym
42
+ RESOURCE = 'resource'.to_sym
43
+ SCOPE = 'scope'.to_sym
44
+ UNIQUE_ID = 'unique_id'.to_sym
45
+ USER_INFO = 'user_info'.to_sym
46
+ USERNAME = 'username'.to_sym
47
+ end
48
+ end
@@ -0,0 +1,96 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2015 Micorosft Corporation
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #-------------------------------------------------------------------------------
22
+
23
+ require_relative './jwt_parameters'
24
+ require_relative './logging'
25
+
26
+ require 'jwt'
27
+ require 'openssl'
28
+ require 'securerandom'
29
+
30
+ module ADAL
31
+ # Converts client certificates into self signed JWTs.
32
+ class SelfSignedJwtFactory
33
+ include JwtParameters
34
+ include Logging
35
+
36
+ ##
37
+ # Constructs a new SelfSignedJwtFactory.
38
+ #
39
+ # @param String client_id
40
+ # The client id of the calling application.
41
+ # @param String token_endpoint
42
+ # The token endpoint that will accept the certificate.
43
+ def initialize(client_id, token_endpoint)
44
+ @client_id = client_id
45
+ @token_endpoint = token_endpoint
46
+ end
47
+
48
+ ##
49
+ # Creates a JWT from a client certificate and signs it with a private key.
50
+ #
51
+ # @param OpenSSL::X509::Certificate certificate
52
+ # The certifcate object to be converted to a JWT and signed for use
53
+ # in an authentication flow.
54
+ # @param OpenSSL::PKey::RSA private_key
55
+ # The private key used to sign the certificate.
56
+ # @return String
57
+ def create_and_sign_jwt(certificate, private_key)
58
+ JWT.encode(payload, private_key, RS256, header(certificate))
59
+ end
60
+
61
+ private
62
+
63
+ # The JWT header for a certificate to be encoded.
64
+ def header(certificate)
65
+ x5t = thumbprint(certificate)
66
+ logger.verbose("Creating self signed JWT header with thumbprint: #{x5t}.")
67
+ { TYPE => TYPE_JWT,
68
+ ALGORITHM => RS256,
69
+ THUMBPRINT => x5t }
70
+ end
71
+
72
+ # The JWT payload.
73
+ def payload
74
+ now = Time.now - 1
75
+ expires = now + 60 * SELF_SIGNED_JWT_LIFETIME
76
+ logger.verbose("Creating self signed JWT payload. Expires: #{expires}. " \
77
+ "NotBefore: #{now}.")
78
+ { AUDIENCE => @token_endpoint,
79
+ ISSUER => @client_id,
80
+ SUBJECT => @client_id,
81
+ NOT_BEFORE => now.to_i,
82
+ EXPIRES_ON => expires.to_i,
83
+ JWT_ID => SecureRandom.uuid }
84
+ end
85
+
86
+ ##
87
+ # Base 64 encoded thumbprint AKA fingerprint AKA SHA1 hash of the
88
+ # DER representation of the cert.
89
+ #
90
+ # @param OpenSSL::X509::Certificate certificate
91
+ # @return String
92
+ def thumbprint(certificate)
93
+ OpenSSL::Digest::SHA1.new(certificate.to_der).base64digest
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,35 @@
1
+ <?xml version="1.0"?>
2
+ <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
3
+ <s:Header>
4
+ <a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
5
+ <a:messageID>urn:uuid:<%= message_id %></a:messageID>
6
+ <a:ReplyTo>
7
+ <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
8
+ </a:ReplyTo>
9
+ <a:To s:mustUnderstand="1"><%= @endpoint %></a:To>
10
+ <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
11
+ <u:Timestamp u:Id="_0">
12
+ <u:Created><%= created.utc.iso8601 %></u:Created>
13
+ <u:Expires><%= expires.utc.iso8601 %></u:Expires>
14
+ </u:Timestamp>
15
+ <o:UsernameToken u:Id="ADALUsernameToken">
16
+ <o:Username><%= username.encode(xml: :text) %></o:Username>
17
+ <o:Password><%= password.encode(xml: :text) %></o:Password>
18
+ </o:UsernameToken>
19
+ </o:Security>
20
+ </s:Header>
21
+ <s:Body>
22
+ <trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
23
+ <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
24
+ <a:EndpointReference>
25
+ <a:Address><%= @applies_to %></a:Address>
26
+ </a:EndpointReference>
27
+ </wsp:AppliesTo>
28
+ <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
29
+ <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
30
+ </trust:RequestSecurityToken>
31
+ </s:Body>
32
+ </s:Envelope>
33
+
34
+
35
+
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0"?>
2
+ <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
3
+ <s:Header>
4
+ <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
5
+ <a:messageID>urn:uuid:<%= message_id %></a:messageID>
6
+ <a:ReplyTo>
7
+ <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
8
+ </a:ReplyTo>
9
+ <a:To s:mustUnderstand="1"><%= @endpoint %></a:To>
10
+ <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
11
+ <u:Timestamp u:Id="_0">
12
+ <u:Created><%= created.utc.iso8601 %></u:Created>
13
+ <u:Expires><%= expires.utc.iso8601 %></u:Expires>
14
+ </u:Timestamp>
15
+ <o:UsernameToken u:Id="ADALUsernameToken">
16
+ <o:Username><%= username.encode(xml: :text) %></o:Username>
17
+ <o:Password><%= password.encode(xml: :text) %></o:Password>
18
+ </o:UsernameToken>
19
+ </o:Security>
20
+ </s:Header>
21
+ <s:Body>
22
+ <trust:RequestSecurityToken xmlns:trust="http://schemas.xmlsoap.org/ws/2005/02/trust">
23
+ <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
24
+ <a:EndpointReference>
25
+ <a:Address><%= @applies_to %></a:Address>
26
+ </a:EndpointReference>
27
+ </wsp:AppliesTo>
28
+ <trust:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</trust:KeyType>
29
+ <trust:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</trust:RequestType>
30
+ </trust:RequestSecurityToken>
31
+ </s:Body>
32
+ </s:Envelope>
@@ -0,0 +1,231 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2015 Micorosft Corporation
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #-------------------------------------------------------------------------------
22
+
23
+ require_relative './cache_driver'
24
+ require_relative './logging'
25
+ require_relative './noop_cache'
26
+ require_relative './oauth_request'
27
+ require_relative './request_parameters'
28
+
29
+ require 'openssl'
30
+
31
+ module ADAL
32
+ # A request for a token that may be fulfilled by a cache or an OAuthRequest
33
+ # to a token endpoint.
34
+ class TokenRequest
35
+ include Logging
36
+ include RequestParameters
37
+
38
+ # An error that signifies an attempt to perform OAuth with a UserIdentifier.
39
+ # UserIdentifiers can only be used to retrieve access tokens from the cache,
40
+ # so if no matching cache token is found, this error is thrown.
41
+ class UserCredentialError < StandardError; end
42
+
43
+ # All accepted grant types. This module can be mixed-in to other classes
44
+ # that require them.
45
+ module GrantType
46
+ AUTHORIZATION_CODE = 'authorization_code'
47
+ CLIENT_CREDENTIALS = 'client_credentials'
48
+ JWT_BEARER = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
49
+ PASSWORD = 'password'
50
+ REFRESH_TOKEN = 'refresh_token'
51
+ SAML1 = 'urn:ietf:params:oauth:grant-type:saml1_1-bearer'
52
+ SAML2 = 'urn:ietf:params:oauth:grant-type:saml2-bearer'
53
+ end
54
+
55
+ ##
56
+ # Constructs a TokenRequest.
57
+ #
58
+ # @param [Authority] authority
59
+ # The Authority providing authorization and token endpoints.
60
+ # @param ClientCredential|ClientAssertion|ClientAssertionCertificate
61
+ # Used to identify the client. Provides a request_parameters method
62
+ # that yields the relevant client credential parameters.
63
+ # @option [TokenCache] token_cache
64
+ # The cache implementation to store tokens. A NoopCache that stores no
65
+ # tokens will be used by default.
66
+ def initialize(authority, client, token_cache = NoopCache.new)
67
+ @authority = authority
68
+ @cache_driver = CacheDriver.new(authority, client, token_cache)
69
+ @client = client
70
+ @token_cache = token_cache
71
+ end
72
+
73
+ public
74
+
75
+ ##
76
+ # Gets a token based solely on the clients credentials that were used to
77
+ # initialize the token request.
78
+ #
79
+ # @param String resource
80
+ # The resource for which the requested access token will provide access.
81
+ # @return TokenResponse
82
+ def get_for_client(resource)
83
+ logger.verbose("TokenRequest getting token for client for #{resource}.")
84
+ request(GRANT_TYPE => GrantType::CLIENT_CREDENTIALS,
85
+ RESOURCE => resource)
86
+ end
87
+
88
+ ##
89
+ # Gets a token based on a previously acquired authentication code.
90
+ #
91
+ # @param String auth_code
92
+ # An authentication code that was previously acquired from an
93
+ # authentication endpoint.
94
+ # @param String redirect_uri
95
+ # The redirect uri that was passed to the authentication endpoint when the
96
+ # auth code was acquired.
97
+ # @optional String resource
98
+ # The resource for which the requested access token will provide access.
99
+ # @return TokenResponse
100
+ def get_with_authorization_code(auth_code, redirect_uri, resource = nil)
101
+ logger.verbose('TokenRequest getting token with authorization code ' \
102
+ "#{auth_code}, redirect_uri #{redirect_uri} and " \
103
+ "resource #{resource}.")
104
+ request(CODE => auth_code,
105
+ GRANT_TYPE => GrantType::AUTHORIZATION_CODE,
106
+ REDIRECT_URI => URI.parse(redirect_uri.to_s),
107
+ RESOURCE => resource)
108
+ end
109
+
110
+ ##
111
+ # Gets a token based on a previously acquired refresh token.
112
+ #
113
+ # @param String refresh_token
114
+ # The refresh token that was previously acquired from a token response.
115
+ # @optional String resource
116
+ # The resource for which the requested access token will provide access.
117
+ # @return TokenResponse
118
+ def get_with_refresh_token(refresh_token, resource = nil)
119
+ logger.verbose('TokenRequest getting token with refresh token digest ' \
120
+ "#{Digest::SHA256.hexdigest refresh_token} and resource " \
121
+ "#{resource}.")
122
+ request_no_cache(GRANT_TYPE => GrantType::REFRESH_TOKEN,
123
+ REFRESH_TOKEN => refresh_token,
124
+ RESOURCE => resource)
125
+ end
126
+
127
+ ##
128
+ # Gets a token based on possessing the users credentials.
129
+ #
130
+ # @param UserCredential|UserIdentifier user_cred
131
+ # Something that can be used to verify the user. Typically a username
132
+ # and password. If it is a UserIdentifier, only the cache will be checked.
133
+ # If a matching token is not there, it will fail.
134
+ # @optional String resource
135
+ # The resource for which the requested access token will provide access.
136
+ # @return TokenResponse
137
+ def get_with_user_credential(user_cred, resource = nil)
138
+ logger.verbose('TokenRequest getting token with user credential ' \
139
+ "#{user_cred} and resource #{resource}.")
140
+ oauth = if user_cred.is_a? UserIdentifier
141
+ lambda do
142
+ fail UserCredentialError,
143
+ 'UserIdentifier can only be used once there is a ' \
144
+ 'matching token in the cache.'
145
+ end
146
+ end || -> {}
147
+ request(user_cred.request_params.merge(RESOURCE => resource), &oauth)
148
+ end
149
+
150
+ private
151
+
152
+ ##
153
+ # The OAuth parameters that are specific to the client for which tokens will
154
+ # be requested.
155
+ #
156
+ # @return Hash
157
+ def client_params
158
+ @client.request_params
159
+ end
160
+
161
+ ##
162
+ # Attempts to fulfill a token request, first via the token cache and then
163
+ # through OAuth.
164
+ #
165
+ # @param Hash params
166
+ # Any additional request parameters that should be used.
167
+ # @return TokenResponse
168
+ def request(params, &block)
169
+ cached_token = check_cache(request_params(params))
170
+ return cached_token if cached_token
171
+ cache_response(request_no_cache(request_params(params), &block))
172
+ end
173
+
174
+ ##
175
+ # Executes an OAuth request based on the params and returns it.
176
+ #
177
+ # @param Hash params
178
+ # Any additional request parameters that should be used.
179
+ # @return TokenResponse
180
+ def request_no_cache(params)
181
+ yield if block_given?
182
+ oauth_request(request_params(params)).execute
183
+ end
184
+
185
+ ##
186
+ # Adds client params to additional params. If there is a conflict, the value
187
+ # from additional_params is used. It can be called multiple times, because
188
+ # request_params(request_params(x)) == request_params(x).
189
+ #
190
+ # @param Hash
191
+ # @return Hash
192
+ def request_params(additional_params)
193
+ client_params.merge(additional_params).select { |_, v| !v.nil? }
194
+ end
195
+
196
+ ##
197
+ # Helper method to chain OAuthRequest and cache operation.
198
+ #
199
+ # @param TokenResponse
200
+ # The token response to cache.
201
+ # @return TokenResponse
202
+ def cache_response(token_response)
203
+ @cache_driver.add(token_response)
204
+ token_response
205
+ end
206
+
207
+ ##
208
+ # Attempts to fulfill the request from @token_cache.
209
+ #
210
+ # @return TokenResponse
211
+ # If the cache contains a valid response it wil be returned as a
212
+ # SuccessResponse. Otherwise returns nil.
213
+ def check_cache(params)
214
+ logger.verbose("TokenRequest checking cache #{@token_cache} for token.")
215
+ result = @cache_driver.find(params)
216
+ logger.info("#{result ? 'Found' : 'Did not find'} token in cache.")
217
+ result
218
+ end
219
+
220
+ ##
221
+ # Constructs an OAuthRequest from the TokenRequest instance.
222
+ #
223
+ # @param Hash params
224
+ # The OAuth parameters specific to the TokenRequest instance.
225
+ # @return OAuthRequest
226
+ def oauth_request(params)
227
+ logger.verbose('Resorting to OAuth to fulfill token request.')
228
+ OAuthRequest.new(@authority.token_endpoint, params)
229
+ end
230
+ end
231
+ end