msidp-endpoint 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da9217f4c3e53be7d57aea0a021671939445d7abe2aeac645948935900b8a289
4
+ data.tar.gz: 01f12f461ef866dbd079c15fc504973d0aa83795e9ecf4d763bf5d3d758d4866
5
+ SHA512:
6
+ metadata.gz: 42f07a9e6127d52203e646df3fc071e9e88ee9be8436eb04118762cc0756678a9d1918f56bb179dbd73a5d23c724a9139d27a1bf6b681cefe8633cbc791b88cf
7
+ data.tar.gz: 7a5e4fd84a032765c5bf05556e08e43d3f63a29cce450d09343cb64aaeb11b2bbcc412f221541f55944a09a69e11be27d2c47086bc62b425398367e69bca3933
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'uri'
5
+ require 'net/http'
6
+ require 'json'
7
+
8
+ module MSIDP
9
+ # Access token issued by Microsoft identity platform.
10
+ class AccessToken
11
+ # @return [String] the token string
12
+ attr_reader :value
13
+ # @return [Date] the expiration date
14
+ attr_reader :expire
15
+ # @return [Array] the scope, a list of permissions.
16
+ attr_reader :scope
17
+ # @return [String] the refresh token (optional)
18
+ attr_reader :refresh_token
19
+ # @return [String] the id token (optional)
20
+ attr_reader :id_token
21
+ # @return [String] the type
22
+ attr_reader :type
23
+
24
+ # Creates a new access token.
25
+ #
26
+ # @param [String] value the access token string.
27
+ # @param [Date] expire the expiration date.
28
+ # @param [Array] scope the list of permissions.
29
+ # @param [String] refresh_token the refresh token.
30
+ # @param [String] id_token the id token.
31
+ # @param [String] type the token type.
32
+ def initialize( # rubocop:disable Metrics/ParameterLists
33
+ value, expire, scope,
34
+ refresh_token = nil, id_token = nil, type = 'Bearer'
35
+ )
36
+ @value = value
37
+ @scope = scope
38
+ @expire = expire
39
+ @refresh_token = refresh_token
40
+ @id_token = id_token
41
+ @type = type
42
+ end
43
+
44
+ # Parses a response from the endpoint and creates an access token object.
45
+ #
46
+ # @param [String,Net::HTTPResponse] res a query string or a HTTP respone.
47
+ # @param [Hash] supplement attributes supplementary to the response.
48
+ def self.parse(res, supplement = {}) # rubocop:disable Metrics/AbcSize
49
+ case res
50
+ when String
51
+ hash = Hash[URI.decode_www_form(res)]
52
+ date = Time.now - 1
53
+ when Net::HTTPResponse
54
+ hash = JSON.parse(res.body)
55
+ date = res.key?('date') ? Time.parse(res['date']) : (Time.now - 1)
56
+ else
57
+ raise TypeError, 'expected String or Net::HTTPResponse'
58
+ end
59
+ hash = supplement.transform_keys(&:to_s).merge(hash)
60
+ AccessToken.new(
61
+ hash['access_token'], date + hash['expires_in'].to_i,
62
+ hash['scope'].split(' '),
63
+ hash['refresh_token'], hash['id_token'],
64
+ hash['token_type']
65
+ )
66
+ end
67
+
68
+ # Checks if the token is not expired.
69
+ #
70
+ # @option kwd [Integer] :in the number of seconds to offset.
71
+ #
72
+ # @example
73
+ # token.valid? in: 5
74
+ def valid?(**kwd)
75
+ @expire > Time.now + kwd.fetch(:in, 0)
76
+ end
77
+
78
+ def to_s
79
+ @value
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MSIDP
4
+ module Endpoint
5
+ VERSION = '0.0.1'
6
+ end
7
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'msidp/error'
6
+ require 'msidp/access_token'
7
+
8
+ module MSIDP
9
+ # Utility methods for Microsoft identity platform endpoints.
10
+ module Endpoint
11
+ # Returns the authorize endpoint URI object.
12
+ #
13
+ # @param [String] tenant a directory tenant in GUID or domain-name format.
14
+ def authorize_uri(tenant)
15
+ uri(tenant, 'authorize')
16
+ end
17
+
18
+ # Returns the token endpoint URI object.
19
+ #
20
+ # @param [String] tenant a directory tenant in GUID or domain-name format.
21
+ def token_uri(tenant)
22
+ uri(tenant, 'token')
23
+ end
24
+
25
+ # Returns an endpoint URI object.
26
+ #
27
+ # @param [String] tenant a directory tenant in GUID or domain-name format.
28
+ # @param [String] endpoint an endpoint.
29
+ def uri(tenant, endpoint)
30
+ URI.parse(
31
+ "https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/#{endpoint}"
32
+ )
33
+ end
34
+
35
+ # Call the authorize endpoint and returns the response.
36
+ #
37
+ # @param [URI::Generic] uri the endpoint URI.
38
+ # @param [Hash] params parameters to the endpoint.
39
+ # @return [Net::HTTPResponse] the endpoint response.
40
+ def authorize(uri, params)
41
+ get(uri, params)
42
+ end
43
+
44
+ # Call the token endpoint and returns an issued access token.
45
+ #
46
+ # @param [URI::Generic] uri the endpoint URI.
47
+ # @param [Hash] params parameters to the endpoint.
48
+ # @param [Hash] supplement supplemental attributres for the access token.
49
+ # @return [AccessToken] the issued access token.
50
+ def token(uri, params, supplement = {})
51
+ res = validate_json_response post(uri, params)
52
+ suppl = params.select { |k| k.intern == :scope }.merge(supplement)
53
+ AccessToken.parse(res, suppl)
54
+ end
55
+
56
+ # Get with parameters from an endpoint and returns the response.
57
+ #
58
+ # @param [URI::Generic] uri the endpoint URI.
59
+ # @param [Hash] params parameters to the endpoint.
60
+ # @param [Hash] headers additional headers.
61
+ # @return [Net::HTTPResponse] the endpoint response.
62
+ def get(uri, params, headers = nil)
63
+ req_uri = URI.parse(uri.request_uri)
64
+ req_uri.query = URI.encode_www_form(params) if params
65
+ req = Net::HTTP::Get.new(req_uri.to_s, headers)
66
+ https_request(uri, req)
67
+ end
68
+
69
+ # Post parameters to an endpoint and returns the response.
70
+ #
71
+ # @param [URI::Generic] uri the endpoint URI.
72
+ # @param [Hash] params parameters to the endpoint.
73
+ # @param [Hash] headers additional headers.
74
+ # @return [Net::HTTPResponse] the endpoint response.
75
+ def post(uri, params, headers = nil)
76
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
77
+ req.form_data = params if params
78
+ https_request(uri, req)
79
+ end
80
+
81
+ # Validate a HTTP response supposed to have the JSON body.
82
+ def validate_json_response(res)
83
+ raise Error, res unless res.is_a? Net::HTTPSuccess
84
+ raise Error, res unless res.content_type&.start_with? 'application/json'
85
+
86
+ res
87
+ end
88
+
89
+ private
90
+
91
+ def https_request(uri, req)
92
+ Net::HTTP.start(uri.host, uri.port,
93
+ use_ssl: true,
94
+ min_version: OpenSSL::SSL::TLS1_2_VERSION) do |http|
95
+ res = http.request(req)
96
+ case res
97
+ when Net::HTTPSuccess, Net::HTTPRedirection
98
+ res
99
+ else
100
+ raise Error, res
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MSIDP
4
+ # Error from Microsoft identity platform.
5
+ class Error < StandardError
6
+ # @return [Net::HTTPResponse] the HTTP response
7
+ attr_reader :response
8
+ # @return [String, Hash] the parsed body of the HTTP response in JSON case,
9
+ # otherwise the raw body.
10
+ attr_reader :body
11
+ # @return [String] the error code
12
+ attr_reader :error
13
+ # @return [String] the error description
14
+ attr_reader :description
15
+
16
+ # @param [Net::HTTPResponse] response the HTTP response
17
+ def initialize(response)
18
+ @response = response
19
+ if response.content_type&.start_with? 'application/json'
20
+ @body = JSON.parse(response.body, symbolize_names: true)
21
+ @error = @body[:error]
22
+ @description = @body[:error_description]
23
+ super(<<-"MSG"
24
+ #{response.code}: #{response.message}
25
+ #{@error}: #{@description}
26
+ MSG
27
+ )
28
+ else
29
+ @body = response.body
30
+ super(<<-"MSG"
31
+ #{response.code}: #{response.message}
32
+ #{@body}
33
+ MSG
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ module Msidp
2
+ class AccessToken
3
+ attr_reader value: String
4
+ attr_reader expire: Time
5
+ attr_reader scope: Array[String]
6
+ attr_reader refresh_token: String?
7
+ attr_reader id_token: String?
8
+ attr_reader type: String
9
+ def initialize: (String value, Time expire, Array[String] scope, ?String refresh_token, ?String id_token, ?String `type`) -> void
10
+ def self.parse: (String | Net::HTTPResponse res, ?Hash[untyped, untyped] supplement) -> AccessToken
11
+ def valid?: (?in: Integer) -> bool
12
+ def to_s: -> String
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Msidp
2
+ module Endpoint
3
+ def authorize_uri: (String tenant) -> URI::Generic
4
+ def token_uri: (String tenant) -> URI::Generic
5
+ def uri: (String tenant, String endpoint) -> URI::Generic
6
+ def authorize: (URI::Generic uri, Hash[untyped, untyped] params) -> Net::HTTPResponse
7
+ def token: (URI::Generic uri, Hash[untyped, untyped] params, ?Hash[untyped, untyped] supplement) -> AccessToken
8
+ def get: (URI::Generic uri, Hash[untyped, untyped] params, ?Hash[String, untyped] headers) -> Net::HTTPResponse
9
+ def post: (URI::Generic uri, Hash[untyped, untyped] params, ?Hash[String, untyped] headers) -> Net::HTTPResponse
10
+ def validate_json_response: (Net::HTTPResponse res) -> Net::HTTPResponse
11
+
12
+ private
13
+ def https_request: (URI::Generic uri, Net::HTTPRequest req) -> Net::HTTPResponse
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Msidp
2
+ class Error < StandardError
3
+ attr_reader response: Net::HTTPResponse
4
+ attr_reader body: String | Hash[Symbol, untyped]
5
+ attr_reader error: String?
6
+ attr_reader description: String?
7
+ def initialize: (Net::HTTPResponse response) -> void
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: msidp-endpoint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - SHIMAYOSHI, Takao
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple class library for Microsoft Identity Platform endpoints.
14
+ email:
15
+ - simayosi@cc.kyushu-u.ac.jp
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/msidp/access_token.rb
21
+ - lib/msidp/endpoint.rb
22
+ - lib/msidp/endpoint/version.rb
23
+ - lib/msidp/error.rb
24
+ - sig/msidp/access_token.rbs
25
+ - sig/msidp/endpoint.rbs
26
+ - sig/msidp/error.rbs
27
+ homepage: https://github.com/simayosi/rb-msidp-endpoint
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ homepage_uri: https://github.com/simayosi/rb-msidp-endpoint
32
+ source_code_uri: https://github.com/simayosi/rb-msidp-endpoint
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.6.0
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.0.3.1
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: MSIDP::Endpoint
52
+ test_files: []