oauth2-client 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.
@@ -0,0 +1,63 @@
1
+ module OAuth2
2
+ class Client
3
+
4
+ attr_reader :host, :connection_options
5
+ attr_accessor :client_id, :client_secret, :connection_client,
6
+ :authorize_path, :token_path, :device_path
7
+
8
+ DEFAULTS_PATHS = {
9
+ :authorize_path => '/oauth2/authorize',
10
+ :token_path => '/oauth2/token',
11
+ :device_path => '/oauth2/device/code',
12
+ }
13
+
14
+ def initialize(host, client_id, client_secret, options={})
15
+ @host = host
16
+ @client_id = client_id
17
+ @client_secret = client_secret
18
+ @connection_options = options.fetch(:connection_options, {})
19
+ @connection_client = options.fetch(:connection_client, OAuth2::HttpConnection)
20
+ DEFAULTS_PATHS.keys.each do |key|
21
+ instance_variable_set(:"@#{key}", options.fetch(key, DEFAULTS_PATHS[key]))
22
+ end
23
+ end
24
+
25
+ def host=(hostname)
26
+ @connection = nil
27
+ @host = hostname
28
+ end
29
+
30
+ def connection_options=(options)
31
+ @connection = nil
32
+ @connection_options = options
33
+ end
34
+
35
+ def implicit
36
+ OAuth2::Grant::Implicit.new(self)
37
+ end
38
+
39
+ def authorization_code
40
+ OAuth2::Grant::AuthorizationCode.new(self)
41
+ end
42
+
43
+ def refresh_token
44
+ OAuth2::Grant::RefreshToken.new(self)
45
+ end
46
+
47
+ def client_credentials
48
+ OAuth2::Grant::ClientCredentials.new(self)
49
+ end
50
+
51
+ def password
52
+ OAuth2::Grant::Password.new(self)
53
+ end
54
+
55
+ def device_code
56
+ OAuth2::Grant::DeviceCode.new(self)
57
+ end
58
+
59
+ def connection
60
+ @connection ||= @connection_client.new(@host, @connection_options)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,188 @@
1
+ begin
2
+ require 'net/https'
3
+ rescue LoadError
4
+ warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
5
+ require 'net/http'
6
+ end
7
+ require 'zlib'
8
+ require 'addressable/uri'
9
+
10
+ module OAuth2
11
+ class HttpConnection
12
+
13
+ class UnhandledHTTPMethodError < StandardError; end
14
+ class UnsupportedSchemeError < StandardError; end
15
+
16
+ NET_HTTP_EXCEPTIONS = [
17
+ EOFError,
18
+ Errno::ECONNABORTED,
19
+ Errno::ECONNREFUSED,
20
+ Errno::ECONNRESET,
21
+ Errno::EINVAL,
22
+ Net::HTTPBadResponse,
23
+ Net::HTTPHeaderSyntaxError,
24
+ Net::ProtocolError,
25
+ SocketError,
26
+ Zlib::GzipFile::Error,
27
+ ]
28
+
29
+ attr_accessor :config, :scheme, :host, :port, :max_redirects, :ssl,
30
+ :user_agent, :accept, :max_redirects, :headers
31
+
32
+ def self.default_options
33
+ {
34
+ :headers => {
35
+ 'Accept' => 'application/json',
36
+ 'User-Agent' => "OAuth2 Ruby Gem #{OAuth2::Version}"
37
+ },
38
+ :ssl => {:verify => true},
39
+ :max_redirects => 5
40
+ }
41
+ end
42
+
43
+ def initialize(url, options={})
44
+ @uri = Addressable::URI.parse(url)
45
+ self.class.default_options.keys.each do |key|
46
+ instance_variable_set(:"@#{key}", options.fetch(key, self.class.default_options[key]))
47
+ end
48
+ end
49
+
50
+ def default_headers
51
+ self.class.default_options[:headers]
52
+ end
53
+
54
+ def scheme=(scheme)
55
+ unless ['http', 'https'].include? scheme
56
+ raise UnsupportedSchemeError.new "#{scheme} is not supported, only http and https"
57
+ end
58
+ @scheme = scheme
59
+ end
60
+
61
+ def scheme
62
+ @scheme ||= @uri.scheme
63
+ end
64
+
65
+ def host
66
+ @host ||= @uri.host
67
+ end
68
+
69
+ def port
70
+ _port = ssl? ? 443 : 80
71
+ @port = @uri.port || _port
72
+ end
73
+
74
+ def absolute_url(path='')
75
+ "#{scheme}://#{host}#{path}"
76
+ end
77
+
78
+ def ssl?
79
+ scheme == "https" ? true : false
80
+ end
81
+
82
+ def ssl=(opts)
83
+ raise "Expected Hash but got #{opts.class.name}" unless opts.is_a?(Hash)
84
+ @ssl.merge!(opts)
85
+ end
86
+
87
+ def http_connection(opts={})
88
+ _host = opts[:host] || host
89
+ _port = opts[:port] || port
90
+ _scheme = opts[:scheme] || scheme
91
+
92
+ @http_client = Net::HTTP.new(_host, _port)
93
+
94
+ configure_ssl(@http_client) if _scheme == 'https'
95
+
96
+ @http_client
97
+ end
98
+
99
+ def send_request(method, path, opts={})
100
+ headers = @headers.merge(opts.fetch(:headers, {}))
101
+ params = opts[:params] || {}
102
+ query = Addressable::URI.form_encode(params)
103
+ method = method.to_s.downcase
104
+ normalized_path = query.empty? ? path : [path, query].join("?")
105
+ client = http_connection(opts.fetch(:connection_options, {}))
106
+
107
+ if (method == 'post' || method == 'put')
108
+ headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
109
+ end
110
+
111
+ case method
112
+ when 'get'
113
+ response = client.get(normalized_path, headers)
114
+ when 'post'
115
+ response = client.post(path, query, headers)
116
+ when 'put'
117
+ response = client.put(path, query, headers)
118
+ when 'delete'
119
+ response = client.delete(normalized_path, headers)
120
+ else
121
+ raise UnhandledHTTPMethodError.new("Unsupported HTTP method, #{method.inspect}")
122
+ end
123
+
124
+ status = response.code.to_i
125
+
126
+ case status
127
+ when 301, 302, 303, 307
128
+ unless redirect_limit_reached?
129
+ if status == 303
130
+ method = :get
131
+ params = nil
132
+ headers.delete('Content-Type')
133
+ end
134
+ redirect_uri = Addressable::URI.parse(response.header['Location'])
135
+ conn = {
136
+ :scheme => redirect_uri.scheme,
137
+ :host => redirect_uri.host,
138
+ :port => redirect_uri.port
139
+ }
140
+ return send_request(method, redirect_uri.path, :params => params, :headers => headers, :connection_options => conn)
141
+ end
142
+ when 100..599
143
+ @redirect_count = 0
144
+ else
145
+ raise "Unhandled status code value of #{response.code}"
146
+ end
147
+ response
148
+ rescue *NET_HTTP_EXCEPTIONS
149
+ raise "Error::ConnectionFailed, $!"
150
+ end
151
+
152
+ private
153
+
154
+ def configure_ssl(http)
155
+ http.use_ssl = true
156
+ http.verify_mode = ssl_verify_mode
157
+ http.cert_store = ssl_cert_store
158
+
159
+ http.cert = ssl[:client_cert] if ssl[:client_cert]
160
+ http.key = ssl[:client_key] if ssl[:client_key]
161
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
162
+ http.ca_path = ssl[:ca_path] if ssl[:ca_path]
163
+ http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
164
+ http.ssl_version = ssl[:version] if ssl[:version]
165
+ end
166
+
167
+ def ssl_verify_mode
168
+ if ssl.fetch(:verify, true)
169
+ OpenSSL::SSL::VERIFY_PEER
170
+ else
171
+ OpenSSL::SSL::VERIFY_NONE
172
+ end
173
+ end
174
+
175
+ def ssl_cert_store
176
+ return ssl[:cert_store] if ssl[:cert_store]
177
+ cert_store = OpenSSL::X509::Store.new
178
+ cert_store.set_default_paths
179
+ cert_store
180
+ end
181
+
182
+ def redirect_limit_reached?
183
+ @redirect_count ||= 0
184
+ @redirect_count += 1
185
+ @redirect_count > @max_redirects
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,3 @@
1
+ module OAuth2
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'oauth2/grant/base'
2
+ require 'oauth2/grant/implicit'
3
+ require 'oauth2/grant/device'
4
+ require 'oauth2/grant/authorization_code'
5
+ require 'oauth2/grant/refresh_token'
6
+ require 'oauth2/grant/client_credentials'
7
+ require 'oauth2/grant/password'
@@ -0,0 +1,71 @@
1
+ module OAuth2
2
+ module Grant
3
+ # Authorization Code Grant
4
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1
5
+ class AuthorizationCode < Base
6
+
7
+ def response_type
8
+ "code"
9
+ end
10
+
11
+ def grant_type
12
+ "authorization_code"
13
+ end
14
+
15
+ # Authorization Request
16
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1.1
17
+ def authorization_path(params={})
18
+ params = params.merge(authorization_params)
19
+ "#{@authorize_path}?#{to_query(params)}"
20
+ end
21
+
22
+ def authorization_url(params={})
23
+ params = params.merge(authorization_params)
24
+ build_url(host, :path => authorize_path, :params => params)
25
+ end
26
+
27
+ # Access Token Request
28
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1.3
29
+ def token_path(params={})
30
+ unless params.empty?
31
+ return "#{@token_path}?#{to_query(params)}"
32
+ end
33
+ @token_path
34
+ end
35
+
36
+ # Retrieve page at authorization path
37
+ #
38
+ # @param [Hash] opts options
39
+ def fetch_authorization_url(opts={})
40
+ opts[:method] ||= :get
41
+ opts[:params] ||= {}
42
+ opts[:params].merge!(authorization_params)
43
+ method = opts.delete(:method) || :get
44
+ make_request(method, @authorize_path, opts)
45
+ end
46
+
47
+ # Retrieve an access token for a given auth code
48
+ #
49
+ # @param [String] code refresh token
50
+ # @param [Hash] params additional params
51
+ # @param [Hash] opts options
52
+ def get_token(code, opts={})
53
+ opts[:params] ||= {}
54
+ opts[:params][:code] = code
55
+ opts[:authenticate] ||= :headers
56
+ method = opts.delete(:method) || :post
57
+ make_request(method, token_path, opts)
58
+ end
59
+
60
+ private
61
+
62
+ # Default authorization request parameters
63
+ def authorization_params
64
+ {
65
+ :response_type => response_type,
66
+ :client_id => @client_id
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ module OAuth2
2
+ module Grant
3
+ class Base
4
+ include OAuth2::UrlHelper
5
+
6
+ class InvalidAuthorizationTypeError < StandardError; end
7
+
8
+ attr_accessor :client_id, :client_secret, :connection, :host,
9
+ :authorize_path, :token_path, :device_path
10
+
11
+ def initialize(client)
12
+ @host = client.host
13
+ @connection = client.connection
14
+ @client_id = client.client_id
15
+ @client_secret = client.client_secret
16
+ @token_path = client.token_path
17
+ @authorize_path = client.authorize_path
18
+ @device_path = client.device_path
19
+ end
20
+
21
+ def make_request(method, path, opts={})
22
+ if auth_type = opts.delete(:authenticate)
23
+ case auth_type.to_sym
24
+ when :body
25
+ opts[:params] ||= {}
26
+ opts[:params].merge!({
27
+ :client_id => @client_id,
28
+ :client_secret => @client_secret
29
+ })
30
+ when :headers
31
+ opts[:headers] ||= {}
32
+ opts[:headers]['Authorization'] = http_basic_encode(@client_id, @client_secret)
33
+ else
34
+ #do nothing
35
+ end
36
+ end
37
+ @connection.send_request(method, path, opts)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ require "base64"
2
+
3
+ module OAuth2
4
+ module Grant
5
+ # Client Credentials Grant
6
+ #
7
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.4
8
+ class ClientCredentials < Base
9
+
10
+ def grant_type
11
+ "client_credentials"
12
+ end
13
+
14
+ # Retrieve an access token for the given client credentials
15
+ #
16
+ # @param [Hash] params additional params
17
+ # @param [Hash] opts options
18
+ def get_token(opts={})
19
+ opts[:params] ||= {}
20
+ opts[:params][:grant_type] = grant_type
21
+ opts[:authenticate] ||= :headers
22
+ method = opts.delete(:method) || :post
23
+ make_request(method, @token_path, opts)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module OAuth2
2
+ module Grant
3
+ # Device Grant
4
+ # @see https://developers.google.com/accounts/docs/OAuth2ForDevices
5
+ class DeviceCode < Base
6
+
7
+ def grant_type
8
+ "http://oauth.net/grant_type/device/1.0"
9
+ end
10
+
11
+ # Generate the authorization path using the given parameters .
12
+ #
13
+ # @param [Hash] query parameters
14
+ def get_code(opts={})
15
+ opts[:params] ||= {}
16
+ opts[:params][:client_id] = @client_id
17
+ method = opts.delete(:method) || :post
18
+ make_request(method, @device_path, opts)
19
+ end
20
+
21
+ # Retrieve an access token given the specified client.
22
+ #
23
+ # @param [Hash] params additional params
24
+ # @param [Hash] opts options
25
+ def get_token(code, opts={})
26
+ opts[:params] ||= {}
27
+ opts[:params].merge!({
28
+ :code => code,
29
+ :grant_type => grant_type
30
+ })
31
+ opts[:authenticate] ||= :headers
32
+ method = opts.delete(:method) || :post
33
+ make_request(method, @token_path, opts)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ module OAuth2
2
+ module Grant
3
+ # Implicit Grant
4
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.2
5
+ class Implicit < Base
6
+
7
+ def response_type
8
+ "token"
9
+ end
10
+
11
+ # Generate a token path using the given parameters .
12
+ #
13
+ # @param [Hash] query parameters
14
+ def token_path(params={})
15
+ params = params.merge(token_params)
16
+ "#{@authorize_path}?#{to_query(params)}"
17
+ end
18
+
19
+ def token_url(params={})
20
+ params = params.merge(token_params)
21
+ build_url(host, :path => authorize_path, :params => params)
22
+ end
23
+
24
+ # Retrieve an access token given the specified client.
25
+ #
26
+ # @param [Hash] params additional params
27
+ # @param [Hash] opts options
28
+ def get_token(opts={})
29
+ opts[:params] ||= {}
30
+ opts[:params].merge!(token_params)
31
+ opts[:authenticate] ||= :headers
32
+ method = opts.delete(:method) || :get
33
+ make_request(method, @token_path, opts)
34
+ end
35
+
36
+ private
37
+
38
+ def token_params
39
+ {
40
+ :response_type => response_type,
41
+ :client_id => @client_id
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end