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
data/lib/adal/util.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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
|
+
# Various helper methods that are useful across several classes and do not fit
|
25
|
+
# into the class hierarchy.
|
26
|
+
module Util
|
27
|
+
def fail_if_arguments_nil(*args)
|
28
|
+
fail ArgumentError, 'Arguments cannot be nil.' if args.any?(&:nil?)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param URI|String
|
32
|
+
# @return Net::HTTP
|
33
|
+
def http(uri)
|
34
|
+
uri = URI.parse(uri.to_s)
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
36
|
+
http.use_ssl = uri.scheme == 'https'
|
37
|
+
http
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Converts every key and value of a hash to string.
|
42
|
+
#
|
43
|
+
# @param Hash
|
44
|
+
# @return Hash
|
45
|
+
def string_hash(hash)
|
46
|
+
hash.map { |k, v| [k.to_s, v.to_s] }.to_h
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/adal/version.rb
ADDED
@@ -0,0 +1,36 @@
|
|
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
|
+
# The current ADAL version.
|
25
|
+
class Version
|
26
|
+
MAJOR = 1 unless defined? MAJOR
|
27
|
+
MINOR = 0 unless defined? MINOR
|
28
|
+
UPDATE = 0 unless defined? UPDATE
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def to_s
|
32
|
+
[MAJOR, MINOR, UPDATE].compact.join('.')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,100 @@
|
|
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 './util'
|
25
|
+
require_relative './wstrust_response'
|
26
|
+
require_relative './xml_namespaces'
|
27
|
+
|
28
|
+
require 'erb'
|
29
|
+
require 'securerandom'
|
30
|
+
|
31
|
+
module ADAL
|
32
|
+
# A request to a WS-Trust endpoint of an ADFS server. Used to obtain a SAML
|
33
|
+
# token that can be exchanged for an access token at a token endpoint.
|
34
|
+
class WSTrustRequest
|
35
|
+
include Logging
|
36
|
+
include Util
|
37
|
+
include XmlNamespaces
|
38
|
+
|
39
|
+
DEFAULT_APPLIES_TO = 'urn:federation:MicrosoftOnline'
|
40
|
+
|
41
|
+
ACTION_TO_RST_TEMPLATE = {
|
42
|
+
WSTRUST_13 =>
|
43
|
+
File.expand_path('../templates/rst.13.xml.erb', __FILE__),
|
44
|
+
WSTRUST_2005 =>
|
45
|
+
File.expand_path('../templates/rst.2005.xml.erb', __FILE__)
|
46
|
+
}
|
47
|
+
|
48
|
+
##
|
49
|
+
# Constructs a new WSTrustRequest.
|
50
|
+
#
|
51
|
+
# @param String|URI endpoint
|
52
|
+
# @param String action
|
53
|
+
# @param String applies_to
|
54
|
+
def initialize(
|
55
|
+
endpoint, action = WSTRUST_13, applies_to = DEFAULT_APPLIES_TO)
|
56
|
+
@applies_to = applies_to
|
57
|
+
@endpoint = URI.parse(endpoint.to_s)
|
58
|
+
@action = action
|
59
|
+
@render = ERB.new(File.read(ACTION_TO_RST_TEMPLATE[action]))
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Performs a WS-Trust RequestSecurityToken request with a username and
|
64
|
+
# password to obtain a federated token.
|
65
|
+
#
|
66
|
+
# @param String username
|
67
|
+
# @param String password
|
68
|
+
# @return WSTrustResponse
|
69
|
+
def execute(username, password)
|
70
|
+
logger.verbose("Making a WSTrust request with action #{@action}.")
|
71
|
+
request = Net::HTTP::Get.new(@endpoint.path)
|
72
|
+
add_headers(request)
|
73
|
+
request.body = rst(username, password)
|
74
|
+
response = http(@endpoint).request(request)
|
75
|
+
if response.code == '200'
|
76
|
+
WSTrustResponse.parse(response.body)
|
77
|
+
else
|
78
|
+
fail WSTrustResponse::WSTrustError, "Failed request: code #{response.code}."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# @param Net::HTTP::Get request
|
85
|
+
def add_headers(request)
|
86
|
+
request.add_field('Content-Type', 'application/soap+xml; charset=utf-8')
|
87
|
+
request.add_field('SOAPAction', @action)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param String username
|
91
|
+
# @param String password
|
92
|
+
# @param String message_id
|
93
|
+
# @return String
|
94
|
+
def rst(username, password, message_id = SecureRandom.uuid)
|
95
|
+
created = Time.now
|
96
|
+
expires = created + 10 * 60 # 10 minute expiration
|
97
|
+
@render.result(binding)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,168 @@
|
|
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 './token_request'
|
25
|
+
require_relative './util'
|
26
|
+
require_relative './xml_namespaces'
|
27
|
+
|
28
|
+
require 'nokogiri'
|
29
|
+
|
30
|
+
module ADAL
|
31
|
+
# Relevant fields from a WS-Trust response.
|
32
|
+
class WSTrustResponse
|
33
|
+
include XmlNamespaces
|
34
|
+
|
35
|
+
class << self
|
36
|
+
include Logging
|
37
|
+
include Util
|
38
|
+
end
|
39
|
+
|
40
|
+
# All recognized SAML token types.
|
41
|
+
module TokenType
|
42
|
+
V1 = 'urn:oasis:names:tc:SAML:1.0:assertion'
|
43
|
+
V2 = 'urn:oasis:names:tc:SAML:2.0:assertion'
|
44
|
+
|
45
|
+
ALL_TYPES = [V1, V2]
|
46
|
+
end
|
47
|
+
|
48
|
+
class WSTrustError < StandardError; end
|
49
|
+
class UnrecognizedTokenTypeError < WSTrustError; end
|
50
|
+
|
51
|
+
ACTION_XPATH = '//s:Envelope/s:Header/a:Action/text()'
|
52
|
+
ERROR_XPATH = '//s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value/text()'
|
53
|
+
FAULT_XPATH = '//s:Envelope/s:Body/s:Fault/s:Reason'
|
54
|
+
SECURITY_TOKEN_XPATH = './trust:RequestedSecurityToken'
|
55
|
+
TOKEN_RESPONSE_XPATH =
|
56
|
+
'//s:Envelope/s:Body/trust:RequestSecurityTokenResponse|//s:Envelope/s:' \
|
57
|
+
'Body/trust:RequestSecurityTokenResponseCollection/trust:RequestSecurit' \
|
58
|
+
'yTokenResponse'
|
59
|
+
TOKEN_TYPE_XPATH = "./*[local-name() = 'TokenType']/text()"
|
60
|
+
TOKEN_XPATH = "./*[local-name() = 'Assertion']"
|
61
|
+
|
62
|
+
##
|
63
|
+
# Parses a WS-Trust response from raw XML into an ADAL::WSTrustResponse
|
64
|
+
# object. Throws an error if the response contains an error.
|
65
|
+
#
|
66
|
+
# @param String|Nokogiri::XML raw_xml
|
67
|
+
# @return ADAL::WSTrustResponse
|
68
|
+
def self.parse(raw_xml)
|
69
|
+
fail_if_arguments_nil(raw_xml)
|
70
|
+
xml = Nokogiri::XML(raw_xml.to_s)
|
71
|
+
parse_error(xml)
|
72
|
+
namespace = ACTION_TO_NAMESPACE[parse_action(xml)]
|
73
|
+
token, token_type = parse_token(xml, namespace)
|
74
|
+
if token && token_type
|
75
|
+
WSTrustResponse.new(format_xml(token), format_xml(token_type))
|
76
|
+
else
|
77
|
+
fail WSTrustError, 'Unable to parse token from response.'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Determines whether the response uses WS-Trust 2005 or WS-Trust 1.3.
|
83
|
+
#
|
84
|
+
# @param Nokogiri::XML::Document xml
|
85
|
+
# @return String
|
86
|
+
def self.parse_action(xml)
|
87
|
+
xml.xpath(ACTION_XPATH, NAMESPACES).to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Checks a WS-Trust response for properly formatted error codes and
|
92
|
+
# descriptions. If found, raises an appropriate exception.
|
93
|
+
#
|
94
|
+
# @param Nokogiri::XML::Document xml
|
95
|
+
def self.parse_error(xml)
|
96
|
+
fault = xml.xpath(FAULT_XPATH, NAMESPACES).first
|
97
|
+
error = xml.xpath(ERROR_XPATH, NAMESPACES).first
|
98
|
+
error = format_xml(error).split(':')[1] || error if error
|
99
|
+
fail WSTrustError, "Fault: #{fault}. Error: #{error}." if fault || error
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param Nokogiri::XML::Document xml
|
103
|
+
# @return String
|
104
|
+
def self.format_xml(xml)
|
105
|
+
xml.to_s.split("\n").map(&:strip).join
|
106
|
+
end
|
107
|
+
private_class_method :format_xml
|
108
|
+
|
109
|
+
# @param Nokogiri::XML::Document
|
110
|
+
# @return [Nokogiri::XML::Element, Nokogiri::XML::Text]
|
111
|
+
def self.parse_token(xml, namespace)
|
112
|
+
xml.xpath(TOKEN_RESPONSE_XPATH, namespace).select do |node|
|
113
|
+
requested_token = node.xpath(SECURITY_TOKEN_XPATH, namespace)
|
114
|
+
case requested_token.size
|
115
|
+
when 0
|
116
|
+
logger.warn('No security token in token response.')
|
117
|
+
next
|
118
|
+
when 1
|
119
|
+
token = requested_token.xpath(TOKEN_XPATH, namespace).first
|
120
|
+
next if token.nil?
|
121
|
+
return token, parse_token_type(node)
|
122
|
+
else
|
123
|
+
fail WSTrustError, 'Found too many RequestedSecurityTokens.'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private_class_method :parse_token
|
128
|
+
|
129
|
+
# @param Nokogiri::XML::Element token_response_node
|
130
|
+
# @return Nokogiri::XML::Text
|
131
|
+
def self.parse_token_type(token_response_node)
|
132
|
+
type = token_response_node.xpath(TOKEN_TYPE_XPATH, NAMESPACES).first
|
133
|
+
logger.warn('No type in token response node.') if type.nil?
|
134
|
+
type
|
135
|
+
end
|
136
|
+
private_class_method :parse_token_type
|
137
|
+
|
138
|
+
attr_reader :token
|
139
|
+
|
140
|
+
##
|
141
|
+
# Constructs a WSTrustResponse.
|
142
|
+
#
|
143
|
+
# @param String token
|
144
|
+
# The content of the returned token.
|
145
|
+
# @param WSTrustResponse::TokenType token_type
|
146
|
+
# The type of the token contained within the WS-Trust response.
|
147
|
+
def initialize(token, token_type)
|
148
|
+
unless TokenType::ALL_TYPES.include? token_type
|
149
|
+
fail UnrecognizedTokenTypeError, token_type
|
150
|
+
end
|
151
|
+
@token = token
|
152
|
+
@token_type = token_type
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Gets the OAuth grant type for the SAML token type of the response.
|
157
|
+
#
|
158
|
+
# @return TokenRequest::GrantType
|
159
|
+
def grant_type
|
160
|
+
case @token_type
|
161
|
+
when TokenType::V1
|
162
|
+
TokenRequest::GrantType::SAML1
|
163
|
+
when TokenType::V2
|
164
|
+
TokenRequest::GrantType::SAML2
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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 'nokogiri'
|
24
|
+
require 'uri'
|
25
|
+
|
26
|
+
module ADAL
|
27
|
+
# WSTrust namespaces for the username/password OAuth flow as well as action
|
28
|
+
# constants and maps to go between bindings, actions and namespaces.
|
29
|
+
module XmlNamespaces
|
30
|
+
NAMESPACES = {
|
31
|
+
'a' => 'http://www.w3.org/2005/08/addressing',
|
32
|
+
'o' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd',
|
33
|
+
's' => 'http://www.w3.org/2003/05/soap-envelope',
|
34
|
+
'u' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd',
|
35
|
+
'wsa' => 'http://www.w3.org/2005/08/addressing',
|
36
|
+
'wsdl' => 'http://schemas.xmlsoap.org/wsdl/',
|
37
|
+
'wsp' => 'http://schemas.xmlsoap.org/ws/2004/09/policy',
|
38
|
+
'soap12' => 'http://schemas.xmlsoap.org/wsdl/soap12/',
|
39
|
+
'sp' => 'http://schemas.xmlsoap.org/ws/2005/07/securitypolicy',
|
40
|
+
'ssp' => 'http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702'
|
41
|
+
}
|
42
|
+
|
43
|
+
NAMESPACES_2005 =
|
44
|
+
NAMESPACES.merge(
|
45
|
+
'trust' => 'http://schemas.xmlsoap.org/ws/2005/02/trust')
|
46
|
+
|
47
|
+
NAMESPACES_13 =
|
48
|
+
NAMESPACES.merge(
|
49
|
+
'trust' => 'http://docs.oasis-open.org/ws-sx/ws-trust/200512')
|
50
|
+
|
51
|
+
WSTRUST_2005 = 'http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue'
|
52
|
+
WSTRUST_13 = 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal'
|
53
|
+
|
54
|
+
ACTION_TO_NAMESPACE = { WSTRUST_13 => NAMESPACES_13,
|
55
|
+
WSTRUST_2005 => NAMESPACES_2005 }
|
56
|
+
|
57
|
+
BINDING_TO_ACTION = {
|
58
|
+
'UserNameWSTrustBinding_IWSTrustFeb2005Async' => WSTRUST_2005,
|
59
|
+
'UserNameWSTrustBinding_IWSTrustFeb2005Async1' => WSTRUST_2005,
|
60
|
+
'UserNameWSTrustBinding_IWSTrust13Async' => WSTRUST_13,
|
61
|
+
'UserNameWSTrustBinding_IWSTrust13Async1' => WSTRUST_13
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
This web app demonstrates the authorization code flow of obtaining access tokens
|
2
|
+
with ADAL.
|
3
|
+
|
4
|
+
To run this web app, you need to:
|
5
|
+
|
6
|
+
1. Register a web application under your Azure Active Directory account.
|
7
|
+
2. Replace `CLIENT_ID`, `CLIENT_SECRET` and `TENANT` with your values.
|
8
|
+
3. Ensure that Ruby and Sinatra are installed on your system.
|
9
|
+
4. Install ADAL with `gem install adal`.
|
10
|
+
5. Start Sinatra with `ruby web_app.rb`.
|
@@ -0,0 +1,139 @@
|
|
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 'adal'
|
24
|
+
require 'json'
|
25
|
+
require 'net/http'
|
26
|
+
require 'securerandom'
|
27
|
+
require 'sinatra'
|
28
|
+
require 'uri'
|
29
|
+
|
30
|
+
AUTHORITY = 'login.windows.net'
|
31
|
+
CLIENT_ID = 'your_client_id_here'
|
32
|
+
CLIENT_SECRET = 'your_client_secret_here'
|
33
|
+
RESOURCE = 'https://graph.windows.net'
|
34
|
+
TENANT = 'your_tenant_here.onmicrosoft.com'
|
35
|
+
|
36
|
+
# AuthenticationContext is specific to a tenant. Our application only cares
|
37
|
+
# about one tenant, so we only need one AuthenticationContext.
|
38
|
+
auth_ctx = ADAL::AuthenticationContext.new(AUTHORITY, TENANT)
|
39
|
+
|
40
|
+
# ADAL::ClientCredential is one of several ways that you can identify your
|
41
|
+
# client application. It contains a client id and a client secret, which are
|
42
|
+
# assigned to you by Azure when you register your application.
|
43
|
+
client_cred = ADAL::ClientCredential.new(CLIENT_ID, CLIENT_SECRET)
|
44
|
+
|
45
|
+
# ADAL supports four logging options: VERBOSE, INFO, WARN and ERROR.
|
46
|
+
# They are defined as constants in ADAL::Logger and are used in ADAL::Logging,
|
47
|
+
# the mix-in factory module that provides Loggers to the various ADAL classes.
|
48
|
+
# By default, log_level = ADAL::Logger::ERROR so only error messages will be
|
49
|
+
# displayed.
|
50
|
+
ADAL::Logging.log_level = ADAL::Logger::VERBOSE
|
51
|
+
|
52
|
+
# ADAL allows you to redirect log outputs to a file or any Ruby object
|
53
|
+
# that implements IO. By default they are sent to STDOUT.
|
54
|
+
ADAL::Logging.log_output = 'my_adal_logs.log'
|
55
|
+
|
56
|
+
# This is Sinatra specific code that allows storing data as a browser cookie.
|
57
|
+
configure do
|
58
|
+
enable :sessions
|
59
|
+
set :session_secret, 'secret'
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public landing page for the web app.
|
63
|
+
get '/' do
|
64
|
+
'This is a public page. Anyone can see it.<br/>' \
|
65
|
+
'If you have credentials, you can view the protected phone book ' \
|
66
|
+
"for your organization <a href='/secure'>here</a>."
|
67
|
+
end
|
68
|
+
|
69
|
+
# In order to access /secure, the user needs an access token for the resource
|
70
|
+
# (https://graph.windows.net) that /secure will be displaying.
|
71
|
+
before '/secure' do
|
72
|
+
redirect to('/auth') unless session[:access_token]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Now that we have an access token, we use it to access the resource.
|
76
|
+
# The details here are specific to your resource.
|
77
|
+
get '/secure' do
|
78
|
+
resource_uri = URI(RESOURCE + '/' + TENANT + '/users?api-version=2013-04-05')
|
79
|
+
headers = { 'authorization' => session[:access_token] }
|
80
|
+
http = Net::HTTP.new(resource_uri.hostname, resource_uri.port)
|
81
|
+
http.use_ssl = true
|
82
|
+
graph = JSON.parse(http.get(resource_uri, headers).body)
|
83
|
+
|
84
|
+
# The resource is displayed to the user.
|
85
|
+
'Here is your phone book.<br/><br/>' +
|
86
|
+
graph['value'].map do |user|
|
87
|
+
%(Given Name: #{user['givenName']}<br/>
|
88
|
+
Surname: #{user['surname']}<br/>
|
89
|
+
Address: #{user['streetAddress']}, #{user['city']}, #{user['country']}
|
90
|
+
<br/><br/>)
|
91
|
+
end.join(' ') +
|
92
|
+
"You can log out <a href='/logout'>here</a>.<br/>" \
|
93
|
+
"Or refresh the token <a href='/refresh'>here.</a>"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Removes the access token from the session. The user will have to authenticate
|
97
|
+
# again if they wish to revisit their phone book.
|
98
|
+
get '/logout' do
|
99
|
+
session.clear
|
100
|
+
redirect to('/')
|
101
|
+
end
|
102
|
+
|
103
|
+
get '/auth' do
|
104
|
+
# Request authorization by redirecting the user. ADAL will help create the
|
105
|
+
# URL, but it will not make the actual request for you. In this case, the
|
106
|
+
# request is made by Sinatra's redirect function.
|
107
|
+
redirect auth_ctx.authorization_request_url(RESOURCE, CLIENT_ID, uri), 303
|
108
|
+
end
|
109
|
+
|
110
|
+
# The authorization code is returned from the authorization server as
|
111
|
+
# params[:code]. We pass this to ADAL to acquire an access_token.
|
112
|
+
post '/auth' do
|
113
|
+
token_response = auth_ctx.acquire_token_with_authorization_code(
|
114
|
+
params[:code], uri, client_cred, RESOURCE)
|
115
|
+
case token_response
|
116
|
+
when ADAL::SuccessResponse
|
117
|
+
# ADAL successfully exchanged the authorization code for an access_token.
|
118
|
+
# The token_response includes other information but we only care about the
|
119
|
+
# access token and the refresh token.
|
120
|
+
session[:access_token] = token_response.access_token
|
121
|
+
session[:refresh_token] = token_response.refresh_token
|
122
|
+
redirect to('/secure')
|
123
|
+
when ADAL::ErrorResponse
|
124
|
+
# ADAL failed to exchange the authorization code for an access_token.
|
125
|
+
redirect to('/')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
before '/refresh' do
|
130
|
+
redirect to('/auth') unless session[:refresh_token]
|
131
|
+
end
|
132
|
+
|
133
|
+
get '/refresh' do
|
134
|
+
token_response = auth_ctx.acquire_token_with_refresh_token(
|
135
|
+
session[:refresh_token], client_cred, RESOURCE)
|
136
|
+
session[:access_token] = token_response.access_token
|
137
|
+
session[:refresh_token] = token_response.refresh_token
|
138
|
+
redirect to('/secure')
|
139
|
+
end
|