Merge branch 'dougforpres-auth-retry'
[arvados.git] / lib / google / api_client / client_secrets.rb
1 # Copyright 2010 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 require 'compat/multi_json'
17
18
19 module Google
20   class APIClient
21     ##
22     # Manages the persistence of client configuration data and secrets. Format
23     # inspired by the Google API Python client.
24     #
25     # @see https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
26     #
27     # @example
28     #   {
29     #     "web": {
30     #       "client_id": "asdfjasdljfasdkjf",
31     #       "client_secret": "1912308409123890",
32     #       "redirect_uris": ["https://www.example.com/oauth2callback"],
33     #       "auth_uri": "https://accounts.google.com/o/oauth2/auth",
34     #       "token_uri": "https://accounts.google.com/o/oauth2/token"
35     #     }
36     #   }
37     #
38     # @example
39     #   {
40     #     "installed": {
41     #       "client_id": "837647042410-75ifg...usercontent.com",
42     #       "client_secret":"asdlkfjaskd",
43     #       "redirect_uris": ["http://localhost", "urn:ietf:oauth:2.0:oob"],
44     #       "auth_uri": "https://accounts.google.com/o/oauth2/auth",
45     #       "token_uri": "https://accounts.google.com/o/oauth2/token"
46     #     }
47     #   }
48     class ClientSecrets
49       
50       ##
51       # Reads client configuration from a file
52       #
53       # @param [String] filename
54       #   Path to file to load
55       #
56       # @return [Google::APIClient::ClientSecrets]
57       #   OAuth client settings
58       def self.load(filename=nil)
59         if filename && File.directory?(filename)
60           search_path = File.expand_path(filename)
61           filename = nil
62         end
63         while filename == nil
64           search_path ||= File.expand_path('.')
65           if File.exists?(File.join(search_path, 'client_secrets.json'))
66             filename = File.join(search_path, 'client_secrets.json')
67           elsif search_path == '/' || search_path =~ /[a-zA-Z]:[\/\\]/
68             raise ArgumentError,
69               'No client_secrets.json filename supplied ' +
70               'and/or could not be found in search path.'
71           else
72             search_path = File.expand_path(File.join(search_path, '..'))
73           end
74         end
75         data = File.open(filename, 'r') { |file| MultiJson.load(file.read) }
76         return self.new(data)
77       end
78
79       ##
80       # Intialize OAuth client settings.
81       #
82       # @param [Hash] options
83       #   Parsed client secrets files
84       def initialize(options={})
85         # Client auth configuration
86         @flow = options[:flow] || options.keys.first.to_s || 'web'
87         fdata = options[@flow]
88         @client_id = fdata[:client_id] || fdata["client_id"]
89         @client_secret = fdata[:client_secret] || fdata["client_secret"]
90         @redirect_uris = fdata[:redirect_uris] || fdata["redirect_uris"]
91         @redirect_uris ||= [fdata[:redirect_uri] || fdata["redirect_uri"]].compact
92         @javascript_origins = (
93           fdata[:javascript_origins] ||
94           fdata["javascript_origins"]
95         )
96         @javascript_origins ||= [fdata[:javascript_origin] || fdata["javascript_origin"]].compact
97         @authorization_uri = fdata[:auth_uri] || fdata["auth_uri"]
98         @authorization_uri ||= fdata[:authorization_uri]
99         @token_credential_uri = fdata[:token_uri] || fdata["token_uri"]
100         @token_credential_uri ||= fdata[:token_credential_uri]
101
102         # Associated token info
103         @access_token = fdata[:access_token] || fdata["access_token"]
104         @refresh_token = fdata[:refresh_token] || fdata["refresh_token"]
105         @id_token = fdata[:id_token] || fdata["id_token"]
106         @expires_in = fdata[:expires_in] || fdata["expires_in"]
107         @expires_at = fdata[:expires_at] || fdata["expires_at"]
108         @issued_at = fdata[:issued_at] || fdata["issued_at"]
109       end
110
111       attr_reader(
112         :flow, :client_id, :client_secret, :redirect_uris, :javascript_origins,
113         :authorization_uri, :token_credential_uri, :access_token,
114         :refresh_token, :id_token, :expires_in, :expires_at, :issued_at
115       )
116
117       ##
118       # Serialize back to the original JSON form
119       #
120       # @return [String]
121       #   JSON
122       def to_json
123         return MultiJson.dump(to_hash)
124       end
125       
126       def to_hash
127         {
128           self.flow => ({
129             'client_id' => self.client_id,
130             'client_secret' => self.client_secret,
131             'redirect_uris' => self.redirect_uris,
132             'javascript_origins' => self.javascript_origins,
133             'auth_uri' => self.authorization_uri,
134             'token_uri' => self.token_credential_uri,
135             'access_token' => self.access_token,
136             'refresh_token' => self.refresh_token,
137             'id_token' => self.id_token,
138             'expires_in' => self.expires_in,
139             'expires_at' => self.expires_at,
140             'issued_at' => self.issued_at
141           }).inject({}) do |accu, (k, v)|
142             # Prunes empty values from JSON output.
143             unless v == nil || (v.respond_to?(:empty?) && v.empty?)
144               accu[k] = v
145             end
146             accu
147           end
148         }
149       end
150       
151       def to_authorization
152         gem 'signet', '>= 0.4.0'
153         require 'signet/oauth_2/client'
154         # NOTE: Do not rely on this default value, as it may change
155         new_authorization = Signet::OAuth2::Client.new
156         new_authorization.client_id = self.client_id
157         new_authorization.client_secret = self.client_secret
158         new_authorization.authorization_uri = (
159           self.authorization_uri ||
160           'https://accounts.google.com/o/oauth2/auth'
161         )
162         new_authorization.token_credential_uri = (
163           self.token_credential_uri ||
164           'https://accounts.google.com/o/oauth2/token'
165         )
166         new_authorization.redirect_uri = self.redirect_uris.first
167
168         # These are supported, but unlikely.
169         new_authorization.access_token = self.access_token
170         new_authorization.refresh_token = self.refresh_token
171         new_authorization.id_token = self.id_token
172         new_authorization.expires_in = self.expires_in
173         new_authorization.issued_at = self.issued_at if self.issued_at
174         new_authorization.expires_at = self.expires_at if self.expires_at
175         return new_authorization
176       end
177     end
178   end
179 end