adal 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|