adal 1.0.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 +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +7 -0
- data/Gemfile +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +39 -0
- data/adal.gemspec +52 -0
- data/contributing.md +127 -0
- data/lib/adal.rb +24 -0
- data/lib/adal/authentication_context.rb +202 -0
- data/lib/adal/authentication_parameters.rb +126 -0
- data/lib/adal/authority.rb +165 -0
- data/lib/adal/cache_driver.rb +171 -0
- data/lib/adal/cached_token_response.rb +190 -0
- data/lib/adal/client_assertion.rb +63 -0
- data/lib/adal/client_assertion_certificate.rb +89 -0
- data/lib/adal/client_credential.rb +46 -0
- data/lib/adal/core_ext.rb +26 -0
- data/lib/adal/core_ext/hash.rb +34 -0
- data/lib/adal/jwt_parameters.rb +39 -0
- data/lib/adal/logger.rb +90 -0
- data/lib/adal/logging.rb +98 -0
- data/lib/adal/memory_cache.rb +95 -0
- data/lib/adal/mex_request.rb +52 -0
- data/lib/adal/mex_response.rb +141 -0
- data/lib/adal/noop_cache.rb +38 -0
- data/lib/adal/oauth_request.rb +76 -0
- data/lib/adal/request_parameters.rb +48 -0
- data/lib/adal/self_signed_jwt_factory.rb +96 -0
- data/lib/adal/templates/rst.13.xml.erb +35 -0
- data/lib/adal/templates/rst.2005.xml.erb +32 -0
- data/lib/adal/token_request.rb +231 -0
- data/lib/adal/token_response.rb +144 -0
- data/lib/adal/user_assertion.rb +57 -0
- data/lib/adal/user_credential.rb +152 -0
- data/lib/adal/user_identifier.rb +83 -0
- data/lib/adal/user_information.rb +49 -0
- data/lib/adal/util.rb +49 -0
- data/lib/adal/version.rb +36 -0
- data/lib/adal/wstrust_request.rb +100 -0
- data/lib/adal/wstrust_response.rb +168 -0
- data/lib/adal/xml_namespaces.rb +64 -0
- data/samples/authorization_code_example/README.md +10 -0
- data/samples/authorization_code_example/web_app.rb +139 -0
- data/samples/client_assertion_certificate_example/README.md +42 -0
- data/samples/client_assertion_certificate_example/app.rb +55 -0
- data/samples/on_behalf_of_example/README.md +35 -0
- data/samples/on_behalf_of_example/native_app.rb +52 -0
- data/samples/on_behalf_of_example/web_api.rb +71 -0
- data/samples/user_credentials_example/README.md +7 -0
- data/samples/user_credentials_example/app.rb +52 -0
- data/spec/adal/authentication_context_spec.rb +186 -0
- data/spec/adal/authentication_parameters_spec.rb +107 -0
- data/spec/adal/authority_spec.rb +122 -0
- data/spec/adal/cache_driver_spec.rb +191 -0
- data/spec/adal/cached_token_response_spec.rb +148 -0
- data/spec/adal/client_assertion_certificate_spec.rb +113 -0
- data/spec/adal/client_assertion_spec.rb +38 -0
- data/spec/adal/core_ext/hash_spec.rb +47 -0
- data/spec/adal/logging_spec.rb +48 -0
- data/spec/adal/memory_cache_spec.rb +107 -0
- data/spec/adal/mex_request_spec.rb +57 -0
- data/spec/adal/mex_response_spec.rb +143 -0
- data/spec/adal/self_signed_jwt_factory_spec.rb +63 -0
- data/spec/adal/token_request_spec.rb +150 -0
- data/spec/adal/token_response_spec.rb +102 -0
- data/spec/adal/user_credential_spec.rb +125 -0
- data/spec/adal/user_identifier_spec.rb +115 -0
- data/spec/adal/wstrust_request_spec.rb +51 -0
- data/spec/adal/wstrust_response_spec.rb +152 -0
- data/spec/fixtures/mex/insecureaddress.xml +924 -0
- data/spec/fixtures/mex/invalid_namespaces.xml +916 -0
- data/spec/fixtures/mex/malformed.xml +914 -0
- data/spec/fixtures/mex/microsoft.xml +916 -0
- data/spec/fixtures/mex/multiple_endpoints.xml +922 -0
- data/spec/fixtures/mex/no_matching_bindings.xml +916 -0
- data/spec/fixtures/mex/no_username_token_policies.xml +914 -0
- data/spec/fixtures/mex/no_wstrust_endpoints.xml +838 -0
- data/spec/fixtures/mex/only_13.xml +842 -0
- data/spec/fixtures/mex/only_2005.xml +842 -0
- data/spec/fixtures/oauth/error.json +1 -0
- data/spec/fixtures/oauth/success.json +1 -0
- data/spec/fixtures/oauth/success_with_id_token.json +1 -0
- data/spec/fixtures/wstrust/error.xml +24 -0
- data/spec/fixtures/wstrust/invalid_namespaces.xml +136 -0
- data/spec/fixtures/wstrust/missing_security_tokens.xml +90 -0
- data/spec/fixtures/wstrust/success.xml +136 -0
- data/spec/fixtures/wstrust/token.xml +1 -0
- data/spec/fixtures/wstrust/too_many_security_tokens.xml +219 -0
- data/spec/fixtures/wstrust/unrecognized_token_type.xml +136 -0
- data/spec/fixtures/wstrust/wstrust.13.xml +1 -0
- data/spec/fixtures/wstrust/wstrust.2005.xml +89 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/fake_data.rb +40 -0
- data/spec/support/fake_token_endpoint.rb +108 -0
- 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
|