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,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
@@ -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