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,39 @@
|
|
|
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
|
+
# Literals used in JWT header and payload.
|
|
25
|
+
module JwtParameters
|
|
26
|
+
ALGORITHM = 'alg'
|
|
27
|
+
AUDIENCE = 'aud'
|
|
28
|
+
EXPIRES_ON = 'exp'
|
|
29
|
+
ISSUER = 'iss'
|
|
30
|
+
JWT_ID = 'jti'
|
|
31
|
+
NOT_BEFORE = 'nbf'
|
|
32
|
+
RS256 = 'RS256'
|
|
33
|
+
SELF_SIGNED_JWT_LIFETIME = 10
|
|
34
|
+
SUBJECT = 'sub'
|
|
35
|
+
THUMBPRINT = 'x5t'
|
|
36
|
+
TYPE = 'typ'
|
|
37
|
+
TYPE_JWT = 'JWT'
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/adal/logger.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
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 'logger'
|
|
24
|
+
|
|
25
|
+
module ADAL
|
|
26
|
+
# Extended version of Ruby's base logger class to support VERBOSE logging.
|
|
27
|
+
# This is consistent with ADAL logging across platforms.
|
|
28
|
+
#
|
|
29
|
+
# The format of a log message is described in the Ruby docs at
|
|
30
|
+
# http://ruby-doc.org/stdlib-2.2.2/libdoc/logger/rdoc/Logger.html as
|
|
31
|
+
# SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message.
|
|
32
|
+
# SeverityID is the first letter of the severity. In the case of ADAL::Logger
|
|
33
|
+
# that means one of {V, I, W, E, F}. The DateTime object uses the to_s method
|
|
34
|
+
# of DateTime from stdlib which is ISO-8601. The ProgName will be the
|
|
35
|
+
# correlation id if one is sent or absent otherwise.
|
|
36
|
+
class Logger < Logger
|
|
37
|
+
SEVS = %w(VERBOSE INFO WARN ERROR FATAL)
|
|
38
|
+
VERBOSE = SEVS.index('VERBOSE')
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Constructs a new Logger.
|
|
42
|
+
#
|
|
43
|
+
# @param String|IO logdev
|
|
44
|
+
# A filename (String) or IO object (STDOUT, STDERR).
|
|
45
|
+
# @param String correlation_id
|
|
46
|
+
# The UUID of the request context.
|
|
47
|
+
def initialize(logdev, correlation_id)
|
|
48
|
+
super(logdev)
|
|
49
|
+
@correlation_id = correlation_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def format_severity(severity)
|
|
53
|
+
SEVS[severity] || 'ANY'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# For some reason, the default logger implementations of #error, #fatal,
|
|
58
|
+
# etc. pass message = nil and progname = <the message> to #add, which
|
|
59
|
+
# interprets that as using the progname as the message. Instead, we will
|
|
60
|
+
# use the message as the message and the progname as the correlation_id.
|
|
61
|
+
#
|
|
62
|
+
# This is purely an internal change, the calling mechanism is exactly the
|
|
63
|
+
# same and it only affects ADAL::Logger, not Logger.
|
|
64
|
+
|
|
65
|
+
# These methods are skipped by the SimpleCov, because it is not our
|
|
66
|
+
# responsibility to test the standard library's logging framework.
|
|
67
|
+
|
|
68
|
+
#:nocov:
|
|
69
|
+
def error(message = nil, &block)
|
|
70
|
+
add(ERROR, message, @correlation_id, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def fatal(message = nil, &block)
|
|
74
|
+
add(FATAL, message, @correlation_id, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def info(message = nil, &block)
|
|
78
|
+
add(INFO, message, @correlation_id, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def verbose(message = nil, &block)
|
|
82
|
+
add(VERBOSE, message, @correlation_id, &block)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def warn(message = nil, &block)
|
|
86
|
+
add(WARN, message, @correlation_id, &block)
|
|
87
|
+
end
|
|
88
|
+
#:nocov:
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/adal/logging.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
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 './logger'
|
|
24
|
+
|
|
25
|
+
require 'securerandom'
|
|
26
|
+
|
|
27
|
+
module ADAL
|
|
28
|
+
# Mix-in module for the ADAL logger. To obtain a logger in class methods the
|
|
29
|
+
# calling class will need to extend this module. To obtain a logger in
|
|
30
|
+
# instance methods the calling will need to include this Module.
|
|
31
|
+
module Logging
|
|
32
|
+
DEFAULT_LOG_LEVEL = Logger::ERROR
|
|
33
|
+
DEFAULT_LOG_OUTPUT = STDOUT
|
|
34
|
+
|
|
35
|
+
@correlation_id = SecureRandom.uuid
|
|
36
|
+
@log_level = DEFAULT_LOG_LEVEL
|
|
37
|
+
@log_output = DEFAULT_LOG_OUTPUT
|
|
38
|
+
|
|
39
|
+
# According to the style guide, class instance variables are preferable to
|
|
40
|
+
# class variables.
|
|
41
|
+
class << self
|
|
42
|
+
attr_accessor :correlation_id
|
|
43
|
+
attr_accessor :log_level
|
|
44
|
+
attr_accessor :log_output
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Sets the ADAL log level.
|
|
49
|
+
#
|
|
50
|
+
# Example usage:
|
|
51
|
+
#
|
|
52
|
+
# ADAL::Logging.log_level = ADAL::Logger::VERBOSE
|
|
53
|
+
#
|
|
54
|
+
def self.log_level=(level)
|
|
55
|
+
unless Logger::SEVS.map.with_index { |_, i| i }.include? level
|
|
56
|
+
fail ArgumentError, "Invalid log level: #{level}."
|
|
57
|
+
end
|
|
58
|
+
@log_level = level
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Sets the ADAL log output. All future logs generated by ADAL will be sent
|
|
63
|
+
# to this location. It is not retroactive.
|
|
64
|
+
#
|
|
65
|
+
# @param IO|String output
|
|
66
|
+
# This can either be STDERR, STDOUT or a String containing a file path.
|
|
67
|
+
def self.log_output=(output)
|
|
68
|
+
output = output.to_s unless output.is_a? IO
|
|
69
|
+
@log_output = output
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Creates one ADAL logger per calling class/module with a specified output.
|
|
74
|
+
# This is to be used within ADAL. Clients will have no use for it.
|
|
75
|
+
#
|
|
76
|
+
# Examples usage:
|
|
77
|
+
#
|
|
78
|
+
# require_relative './logging'
|
|
79
|
+
#
|
|
80
|
+
# module ADAL
|
|
81
|
+
# module SomeModule
|
|
82
|
+
# include Logging
|
|
83
|
+
#
|
|
84
|
+
# def something_bad
|
|
85
|
+
# logger.error('An error message')
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# @param output
|
|
91
|
+
# STDERR, STDOUT or the file name as a string.
|
|
92
|
+
def logger
|
|
93
|
+
@logger ||= ADAL::Logger.new(Logging.log_output, Logging.correlation_id)
|
|
94
|
+
@logger.level = Logging.log_level || DEFAULT_LOG_LEVEL
|
|
95
|
+
@logger
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
|
|
25
|
+
module ADAL
|
|
26
|
+
# A simple cache implementation that is not persisted across application runs.
|
|
27
|
+
class MemoryCache
|
|
28
|
+
include Logging
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@entries = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attr_accessor :entries
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Adds an array of objects to the cache.
|
|
38
|
+
#
|
|
39
|
+
# @param Array
|
|
40
|
+
# The entries to add.
|
|
41
|
+
# @return Array
|
|
42
|
+
# The entries after the addition.
|
|
43
|
+
def add(entries)
|
|
44
|
+
entries = Array(entries) # If entries is an array, this is a no-op.
|
|
45
|
+
old_size = @entries.size
|
|
46
|
+
@entries |= entries
|
|
47
|
+
logger.verbose("Added #{entries.size - old_size} new entries to cache.")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# By default, matches all entries.
|
|
52
|
+
#
|
|
53
|
+
# @param Block
|
|
54
|
+
# A matcher on the token list.
|
|
55
|
+
# @return Array
|
|
56
|
+
# The matching tokens.
|
|
57
|
+
def find(&query)
|
|
58
|
+
query ||= proc { true }
|
|
59
|
+
@entries.select(&query)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Removes an array of objects from the cache.
|
|
64
|
+
#
|
|
65
|
+
# @param Array
|
|
66
|
+
# The entries to remove.
|
|
67
|
+
# @return Array
|
|
68
|
+
# The remaining entries.
|
|
69
|
+
def remove(entries)
|
|
70
|
+
@entries -= Array(entries)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Converts the cache entries into one JSON string.
|
|
75
|
+
#
|
|
76
|
+
# @param JSON::Ext::Generator::State
|
|
77
|
+
# @return String
|
|
78
|
+
def to_json(_ = nil)
|
|
79
|
+
JSON.unparse(entries)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Reconstructs the cache from JSON that was previously serialized.
|
|
84
|
+
#
|
|
85
|
+
# @param JSON json
|
|
86
|
+
# @return MemoryCache
|
|
87
|
+
def self.from_json(json)
|
|
88
|
+
cache = MemoryCache.new
|
|
89
|
+
cache.entries = JSON.parse(json).map do |e|
|
|
90
|
+
CachedTokenResponse.from_json(e)
|
|
91
|
+
end
|
|
92
|
+
cache
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
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 './mex_response'
|
|
24
|
+
require_relative './util'
|
|
25
|
+
|
|
26
|
+
require 'net/http'
|
|
27
|
+
require 'uri'
|
|
28
|
+
|
|
29
|
+
module ADAL
|
|
30
|
+
# A request to a Metadata Exchange endpoint of an ADFS server. Used to obtain
|
|
31
|
+
# the WSTrust endpoint for username and password authentication of federated
|
|
32
|
+
# users.
|
|
33
|
+
class MexRequest
|
|
34
|
+
include Util
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Constructs a MexRequest object for a specific URL endpoint.
|
|
38
|
+
#
|
|
39
|
+
# @param String|URI endpoint
|
|
40
|
+
# The Metadata Exchange endpoint.
|
|
41
|
+
def initialize(endpoint)
|
|
42
|
+
@endpoint = URI.parse(endpoint.to_s)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return MexResponse
|
|
46
|
+
def execute
|
|
47
|
+
request = Net::HTTP::Get.new(@endpoint.path)
|
|
48
|
+
request.add_field('Content-Type', 'application/soap+xml')
|
|
49
|
+
MexResponse.parse(http(@endpoint).request(request).body)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
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 './xml_namespaces'
|
|
25
|
+
|
|
26
|
+
require 'nokogiri'
|
|
27
|
+
require 'uri'
|
|
28
|
+
|
|
29
|
+
module ADAL
|
|
30
|
+
# Relevant fields from a Mex response.
|
|
31
|
+
class MexResponse
|
|
32
|
+
include XmlNamespaces
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
include Logging
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class MexError < StandardError; end
|
|
39
|
+
|
|
40
|
+
POLICY_ID_XPATH =
|
|
41
|
+
'//wsdl:definitions/wsp:Policy[./wsp:ExactlyOne/wsp:All/sp:SignedSuppor' \
|
|
42
|
+
'tingTokens/wsp:Policy/sp:UsernameToken/wsp:Policy/sp:WssUsernameToken1' \
|
|
43
|
+
'0]/@u:Id|//wsdl:definitions/wsp:Policy[./wsp:ExactlyOne/wsp:All/ssp:Si' \
|
|
44
|
+
'gnedEncryptedSupportingTokens/wsp:Policy/ssp:UsernameToken/wsp:Policy/' \
|
|
45
|
+
'ssp:WssUsernameToken10]/@u:Id'
|
|
46
|
+
BINDING_XPATH = '//wsdl:definitions/wsdl:binding[./wsp:PolicyReference]'
|
|
47
|
+
PORT_XPATH = '//wsdl:definitions/wsdl:service/wsdl:port'
|
|
48
|
+
ADDRESS_XPATH = './soap12:address/@location'
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Parses the XML string response from the Metadata Exchange endpoint into
|
|
52
|
+
# a MexResponse object.
|
|
53
|
+
#
|
|
54
|
+
# @param String response
|
|
55
|
+
# @return MexResponse
|
|
56
|
+
def self.parse(response)
|
|
57
|
+
xml = Nokogiri::XML(response)
|
|
58
|
+
policy_ids = parse_policy_ids(xml)
|
|
59
|
+
bindings = parse_bindings(xml, policy_ids)
|
|
60
|
+
endpoint, binding = parse_endpoint_and_binding(xml, bindings)
|
|
61
|
+
MexResponse.new(endpoint, binding)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param Nokogiri::XML::Document xml
|
|
65
|
+
# @param Array[String] policy_ids
|
|
66
|
+
# @return Array[String]
|
|
67
|
+
def self.parse_bindings(xml, policy_ids)
|
|
68
|
+
matching_bindings = xml.xpath(BINDING_XPATH, NAMESPACES).map do |node|
|
|
69
|
+
reference_uri = node.xpath('./wsp:PolicyReference/@URI', NAMESPACES)
|
|
70
|
+
node.xpath('./@name').to_s if policy_ids.include? reference_uri.to_s
|
|
71
|
+
end.compact
|
|
72
|
+
fail MexError, 'No matching bindings found.' if matching_bindings.empty?
|
|
73
|
+
matching_bindings
|
|
74
|
+
end
|
|
75
|
+
private_class_method :parse_bindings
|
|
76
|
+
|
|
77
|
+
# @param Nokogiri::XML::Document xml
|
|
78
|
+
# @param Array[String] bindings
|
|
79
|
+
# @return Array[[String, String]]
|
|
80
|
+
def self.parse_all_endpoints(xml, bindings)
|
|
81
|
+
endpoints = xml.xpath(PORT_XPATH, NAMESPACES).map do |node|
|
|
82
|
+
binding = node.attr('binding').split(':').last
|
|
83
|
+
if bindings.include? binding
|
|
84
|
+
[node.xpath(ADDRESS_XPATH, NAMESPACES).to_s, binding]
|
|
85
|
+
end
|
|
86
|
+
end.compact
|
|
87
|
+
endpoints
|
|
88
|
+
end
|
|
89
|
+
private_class_method :parse_all_endpoints
|
|
90
|
+
|
|
91
|
+
# @param Nokogiri::XML::Document xml
|
|
92
|
+
# @param Array[String] bindings
|
|
93
|
+
# @return [String, String]
|
|
94
|
+
def self.parse_endpoint_and_binding(xml, bindings)
|
|
95
|
+
endpoints = parse_all_endpoints(xml, bindings)
|
|
96
|
+
case endpoints.size
|
|
97
|
+
when 0
|
|
98
|
+
fail MexError, 'No valid WS-Trust endpoints found.'
|
|
99
|
+
when 1
|
|
100
|
+
else
|
|
101
|
+
logger.info('Multiple WS-Trust endpoints were found in the mex ' \
|
|
102
|
+
'response. Only one was used.')
|
|
103
|
+
end
|
|
104
|
+
prefer_13(endpoints).first
|
|
105
|
+
end
|
|
106
|
+
private_class_method :parse_endpoint_and_binding
|
|
107
|
+
|
|
108
|
+
# @param Nokogiri::XML::Document xml
|
|
109
|
+
# @return Array[String]
|
|
110
|
+
def self.parse_policy_ids(xml)
|
|
111
|
+
policy_ids = xml.xpath(POLICY_ID_XPATH, NAMESPACES)
|
|
112
|
+
.map { |attr| "\##{attr.value}" }
|
|
113
|
+
fail MexError, 'No username token policy nodes.' if policy_ids.empty?
|
|
114
|
+
policy_ids
|
|
115
|
+
end
|
|
116
|
+
private_class_method :parse_policy_ids
|
|
117
|
+
|
|
118
|
+
# @param Array[String, String] endpoints
|
|
119
|
+
# @return Array[String, String] endpoints
|
|
120
|
+
def self.prefer_13(endpoints)
|
|
121
|
+
only13 = endpoints.select { |_, b| BINDING_TO_ACTION[b] == WSTRUST_13 }
|
|
122
|
+
only13.empty? ? endpoints : only13
|
|
123
|
+
end
|
|
124
|
+
private_class_method :prefer_13
|
|
125
|
+
|
|
126
|
+
attr_reader :action
|
|
127
|
+
attr_reader :wstrust_url
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Constructs a new MexResponse.
|
|
131
|
+
#
|
|
132
|
+
# @param String|URI wstrust_url
|
|
133
|
+
# @param String action
|
|
134
|
+
def initialize(wstrust_url, binding)
|
|
135
|
+
@action = BINDING_TO_ACTION[binding]
|
|
136
|
+
@wstrust_url = URI.parse(wstrust_url.to_s)
|
|
137
|
+
return if @wstrust_url.instance_of? URI::HTTPS
|
|
138
|
+
fail ArgumentError, 'Mex is only done over HTTPS.'
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|