OLD | NEW |
1 /* | 1 /* |
2 Copyright 2013 Google Inc | 2 Copyright 2013 Google Inc |
3 | 3 |
4 Licensed under the Apache License, Version 2.0 (the "License"); | 4 Licensed under the Apache License, Version 2.0 (the "License"); |
5 you may not use this file except in compliance with the License. | 5 you may not use this file except in compliance with the License. |
6 You may obtain a copy of the License at | 6 You may obtain a copy of the License at |
7 | 7 |
8 http://www.apache.org/licenses/LICENSE-2.0 | 8 http://www.apache.org/licenses/LICENSE-2.0 |
9 | 9 |
10 Unless required by applicable law or agreed to in writing, software | 10 Unless required by applicable law or agreed to in writing, software |
11 distributed under the License is distributed on an "AS IS" BASIS, | 11 distributed under the License is distributed on an "AS IS" BASIS, |
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 See the License for the specific language governing permissions and | 13 See the License for the specific language governing permissions and |
14 limitations under the License. | 14 limitations under the License. |
15 */ | 15 */ |
16 | 16 |
17 using System; | 17 using System; |
18 using System.Collections.Generic; | 18 using System.Collections.Generic; |
19 using System.Net; | |
20 using System.Net.Http; | |
21 using System.Security.Cryptography; | 19 using System.Security.Cryptography; |
22 using System.Security.Cryptography.X509Certificates; | 20 using System.Security.Cryptography.X509Certificates; |
23 using System.Text; | 21 using System.Text; |
24 using System.Threading; | 22 using System.Threading; |
25 using System.Threading.Tasks; | 23 using System.Threading.Tasks; |
26 | 24 |
27 using Google.Apis.Auth.OAuth2.Requests; | 25 using Google.Apis.Auth.OAuth2.Requests; |
28 using Google.Apis.Auth.OAuth2.Responses; | |
29 using Google.Apis.Http; | |
30 using Google.Apis.Json; | 26 using Google.Apis.Json; |
31 using Google.Apis.Logging; | 27 using Google.Apis.Logging; |
32 using Google.Apis.Util; | 28 using Google.Apis.Util; |
33 | 29 |
34 namespace Google.Apis.Auth.OAuth2 | 30 namespace Google.Apis.Auth.OAuth2 |
35 { | 31 { |
36 /// <summary> | 32 /// <summary> |
37 /// Google OAuth 2.0 credential for accessing protected resources using an a
ccess token. The Google OAuth 2.0· | 33 /// Google OAuth 2.0 credential for accessing protected resources using an a
ccess token. The Google OAuth 2.0· |
38 /// Authorization Server supports server-to-server interactions such as thos
e between a web application and Google | 34 /// Authorization Server supports server-to-server interactions such as thos
e between a web application and Google |
39 /// Cloud Storage. The requesting application has to prove its own identity
to gain access to an API, and an· | 35 /// Cloud Storage. The requesting application has to prove its own identity
to gain access to an API, and an· |
40 /// end-user doesn't have to be involved.· | 36 /// end-user doesn't have to be involved.· |
41 /// <para> | 37 /// <para> |
42 /// Take a look in https://developers.google.com/accounts/docs/OAuth2Service
Account for more details. | 38 /// Take a look in https://developers.google.com/accounts/docs/OAuth2Service
Account for more details. |
43 /// </para> | 39 /// </para> |
44 /// </summary> | 40 /// </summary> |
45 public class ServiceAccountCredential : IHttpExecuteInterceptor, IHttpUnsucc
essfulResponseHandler, | 41 public class ServiceAccountCredential : ServiceCredential |
46 IConfigurableHttpClientInitializer | |
47 { | 42 { |
48 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<ServiceAccountCredential>(); | |
49 | |
50 /// <summary>An initializer class for the service account credential. </
summary> | 43 /// <summary>An initializer class for the service account credential. </
summary> |
51 public class Initializer | 44 public class Initializer : ServiceCredential.Initializer |
52 { | 45 { |
53 /// <summary>Gets the service account ID (typically an e-mail addres
s).</summary> | 46 /// <summary>Gets the service account ID (typically an e-mail addres
s).</summary> |
54 public string Id { get; private set; } | 47 public string Id { get; private set; } |
55 | 48 |
56 /// <summary>Gets the token server URL.</summary> | |
57 public string TokenServerUrl { get; private set; } | |
58 | |
59 /// <summary> | 49 /// <summary> |
60 /// Gets or sets the email address of the user the application is tr
ying to impersonate in the service· | 50 /// Gets or sets the email address of the user the application is tr
ying to impersonate in the service· |
61 /// account flow or <c>null</c>. | 51 /// account flow or <c>null</c>. |
62 /// </summary> | 52 /// </summary> |
63 public string User { get; set; } | 53 public string User { get; set; } |
64 | 54 |
65 /// <summary>Gets the scopes which indicate API access your applicat
ion is requesting.</summary> | 55 /// <summary>Gets the scopes which indicate API access your applicat
ion is requesting.</summary> |
66 public IEnumerable<string> Scopes { get; set; } | 56 public IEnumerable<string> Scopes { get; set; } |
67 | 57 |
68 /// <summary> | 58 /// <summary> |
69 /// Gets or sets the clock. The clock is used to determine if the to
ken has expired, if so we will try to | |
70 /// refresh it. The default value is <see cref="Google.Apis.Util.Sys
temClock.Default"/>. | |
71 /// </summary> | |
72 public IClock Clock { get; set; } | |
73 | |
74 /// <summary> | |
75 /// Gets or sets the key which is used to sign the request, as speci
fied in | 59 /// Gets or sets the key which is used to sign the request, as speci
fied in |
76 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
#computingsignature. | 60 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
#computingsignature. |
77 /// </summary> | 61 /// </summary> |
78 public RSACryptoServiceProvider Key { get; set; } | 62 public RSACryptoServiceProvider Key { get; set; } |
79 | 63 |
80 /// <summary> | |
81 /// Gets or sets the method for presenting the access token to the r
esource server. | |
82 /// The default value is <see cref="BearerToken.AuthorizationHeaderA
ccessMethod"/>. | |
83 /// </summary> | |
84 public IAccessMethod AccessMethod { get; set; } | |
85 | |
86 /// <summary> | |
87 /// Gets or sets the factory for creating a <see cref="System.Net.Ht
tp.HttpClient"/> instance. | |
88 /// </summary> | |
89 public IHttpClientFactory HttpClientFactory { get; set; } | |
90 | |
91 /// <summary> | |
92 /// Get or sets the exponential back-off policy. Default value is <
c>UnsuccessfulResponse503</c>, which· | |
93 /// means that exponential back-off is used on 503 abnormal HTTP res
ponses. | |
94 /// If the value is set to <c>None</c>, no exponential back-off poli
cy is used, and it's up to the user to | |
95 /// configure the <see cref="Google.Apis.Http.ConfigurableMessageHan
dler"/> in an | |
96 /// <see cref="Google.Apis.Http.IConfigurableHttpClientInitializer"/
> to set a specific back-off | |
97 /// implementation (using <see cref="Google.Apis.Http.BackOffHandler
"/>). | |
98 /// </summary> | |
99 public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { ge
t; set; } | |
100 | |
101 /// <summary>Constructs a new initializer using the given id.</summa
ry> | 64 /// <summary>Constructs a new initializer using the given id.</summa
ry> |
102 public Initializer(string id) | 65 public Initializer(string id) |
103 : this(id, GoogleAuthConsts.TokenUrl) { } | 66 : this(id, GoogleAuthConsts.TokenUrl) { } |
104 | 67 |
105 /// <summary>Constructs a new initializer using the given id and the
token server URL.</summary> | 68 /// <summary>Constructs a new initializer using the given id and the
token server URL.</summary> |
106 public Initializer(string id, string tokenServerUrl) | 69 public Initializer(string id, string tokenServerUrl) : base(tokenSer
verUrl) |
107 { | 70 { |
108 Id = id; | 71 Id = id; |
109 TokenServerUrl = tokenServerUrl; | |
110 | |
111 AccessMethod = new BearerToken.AuthorizationHeaderAccessMethod()
; | |
112 Clock = SystemClock.Default; | |
113 DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.Unsuc
cessfulResponse503; | |
114 Scopes = new List<string>(); | 72 Scopes = new List<string>(); |
115 } | 73 } |
116 | 74 |
117 /// <summary>Extracts a <see cref="Key"/> from the given certificate
.</summary> | 75 /// <summary>Extracts a <see cref="Key"/> from the given certificate
.</summary> |
118 public Initializer FromCertificate(X509Certificate2 certificate) | 76 public Initializer FromCertificate(X509Certificate2 certificate) |
119 { | 77 { |
120 // Workaround to correctly cast the private key as a RSACryptoSe
rviceProvider type 24. | 78 // Workaround to correctly cast the private key as a RSACryptoSe
rviceProvider type 24. |
121 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certifi
cate.PrivateKey; | 79 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certifi
cate.PrivateKey; |
122 byte[] privateKeyBlob = rsa.ExportCspBlob(true); | 80 byte[] privateKeyBlob = rsa.ExportCspBlob(true); |
123 Key = new RSACryptoServiceProvider(); | 81 Key = new RSACryptoServiceProvider(); |
124 Key.ImportCspBlob(privateKeyBlob); | 82 Key.ImportCspBlob(privateKeyBlob); |
125 return this; | 83 return this; |
126 } | 84 } |
127 } | 85 } |
128 | 86 |
129 #region Readonly fields | 87 #region Readonly fields |
130 | 88 |
131 private readonly string id; | 89 private readonly string id; |
132 private readonly string tokenServerUrl; | |
133 private readonly string user; | 90 private readonly string user; |
134 private readonly IEnumerable<string> scopes; | 91 private readonly IEnumerable<string> scopes; |
135 private readonly IClock clock; | |
136 private readonly IAccessMethod accessMethod; | |
137 private readonly ConfigurableHttpClient httpClient; | |
138 private readonly RSACryptoServiceProvider key; | 92 private readonly RSACryptoServiceProvider key; |
139 | 93 |
140 #endregion | 94 #endregion |
141 | 95 |
142 /// <summary>Gets the service account ID (typically an e-mail address).<
/summary> | 96 /// <summary>Gets the service account ID (typically an e-mail address).<
/summary> |
143 public string Id { get { return id; } } | 97 public string Id { get { return id; } } |
144 | 98 |
145 /// <summary>Gets the token server URL.</summary> | |
146 public string TokenServerUrl { get { return tokenServerUrl; } } | |
147 | |
148 /// <summary> | 99 /// <summary> |
149 /// Gets the email address of the user the application is trying to impe
rsonate in the service account flow· | 100 /// Gets the email address of the user the application is trying to impe
rsonate in the service account flow· |
150 /// or <c>null</c>. | 101 /// or <c>null</c>. |
151 /// </summary> | 102 /// </summary> |
152 public string User { get { return user; } } | 103 public string User { get { return user; } } |
153 | 104 |
154 /// <summary>Gets the service account scopes.</summary> | 105 /// <summary>Gets the service account scopes.</summary> |
155 public IEnumerable<string> Scopes { get { return scopes; } } | 106 public IEnumerable<string> Scopes { get { return scopes; } } |
156 | 107 |
157 /// <summary> | 108 /// <summary> |
158 /// Gets the clock. The clock is used to determine if the token has expi
red, if so we will try to refresh it.· | |
159 /// </summary> | |
160 public IClock Clock { get { return clock; } } | |
161 | |
162 /// <summary>Gets the method for presenting the access token to the reso
urce server.</summary> | |
163 public IAccessMethod AccessMethod { get { return accessMethod; } } | |
164 | |
165 /// <summary>Gets the HTTP client used to make authentication requests t
o the server.</summary> | |
166 public ConfigurableHttpClient HttpClient { get { return httpClient; } } | |
167 | |
168 /// <summary> | |
169 /// Gets the key which is used to sign the request, as specified in | 109 /// Gets the key which is used to sign the request, as specified in |
170 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#com
putingsignature. | 110 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#com
putingsignature. |
171 /// </summary> | 111 /// </summary> |
172 public RSACryptoServiceProvider Key { get { return key; } } | 112 public RSACryptoServiceProvider Key { get { return key; } } |
173 | 113 |
174 private TokenResponse token; | |
175 private object lockObject = new object(); | |
176 | |
177 /// <summary>Gets the token response which contains the access token.</s
ummary> | |
178 public TokenResponse Token | |
179 { | |
180 get | |
181 { | |
182 lock (lockObject) | |
183 { | |
184 return token; | |
185 } | |
186 } | |
187 private set | |
188 { | |
189 lock (lockObject) | |
190 { | |
191 token = value; | |
192 } | |
193 } | |
194 } | |
195 | |
196 /// <summary>Constructs a new service account credential using the given
initializer.</summary> | 114 /// <summary>Constructs a new service account credential using the given
initializer.</summary> |
197 /// <param name="initializer"></param> | 115 /// <param name="initializer"></param> |
198 public ServiceAccountCredential(Initializer initializer) | 116 public ServiceAccountCredential(Initializer initializer) : base(initiali
zer) |
199 { | 117 { |
200 id = initializer.Id.ThrowIfNullOrEmpty("initializer.Id"); | 118 id = initializer.Id.ThrowIfNullOrEmpty("initializer.Id"); |
201 user = initializer.User; | 119 user = initializer.User; |
202 scopes = initializer.Scopes; | 120 scopes = initializer.Scopes; |
203 tokenServerUrl = initializer.TokenServerUrl; | |
204 accessMethod = initializer.AccessMethod.ThrowIfNull("initializer.Acc
essMethod"); | |
205 clock = initializer.Clock.ThrowIfNull("initializer.Clock"); | |
206 | |
207 // Set the HTTP client. | |
208 var httpArgs = new CreateHttpClientArgs(); | |
209 | |
210 // Add exponential back-off initializer if necessary. | |
211 if (initializer.DefaultExponentialBackOffPolicy != ExponentialBackOf
fPolicy.None) | |
212 { | |
213 httpArgs.Initializers.Add( | |
214 new ExponentialBackOffInitializer(initializer.DefaultExponen
tialBackOffPolicy, | |
215 () => new BackOffHandler(new ExponentialBackOff()))); | |
216 } | |
217 httpClient = (initializer.HttpClientFactory ?? new HttpClientFactory
()).CreateHttpClient(httpArgs); | |
218 key = initializer.Key.ThrowIfNull("initializer.Key"); | 121 key = initializer.Key.ThrowIfNull("initializer.Key"); |
219 } | 122 } |
220 | 123 |
221 public void Initialize(ConfigurableHttpClient httpClient) | 124 #region ServiceCredential overrides |
222 { | |
223 httpClient.MessageHandler.ExecuteInterceptors.Add(this); | |
224 httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); | |
225 } | |
226 | |
227 public async Task InterceptAsync(HttpRequestMessage request, Cancellatio
nToken cancellationToken) | |
228 { | |
229 if (Token == null || Token.IsExpired(Clock)) | |
230 { | |
231 Logger.Debug("Token has expired, trying to get a new one."); | |
232 if (!await RequestAccessTokenAsync(cancellationToken).ConfigureA
wait(false)) | |
233 { | |
234 throw new InvalidOperationException("The access token has ex
pired but we can't refresh it"); | |
235 } | |
236 Logger.Info("New access token was received successfully"); | |
237 } | |
238 | |
239 AccessMethod.Intercept(request, Token.AccessToken); | |
240 } | |
241 | |
242 public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseAr
gs args) | |
243 { | |
244 // TODO(peleyal): check WWW-Authenticate header. | |
245 if (args.Response.StatusCode == HttpStatusCode.Unauthorized) | |
246 { | |
247 return !Object.Equals(Token.AccessToken, AccessMethod.GetAccessT
oken(args.Request)) | |
248 || await RequestAccessTokenAsync(args.CancellationToken).Con
figureAwait(false); | |
249 } | |
250 | |
251 return false; | |
252 } | |
253 | 125 |
254 /// <summary> | 126 /// <summary> |
255 /// Requests a new token as specified in· | 127 /// Requests a new token as specified in· |
256 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#mak
ingrequest. | 128 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#mak
ingrequest. |
257 /// </summary> | 129 /// </summary> |
258 /// <param name="taskCancellationToken">Cancellation token to cancel ope
ration.</param> | 130 /// <param name="taskCancellationToken">Cancellation token to cancel ope
ration.</param> |
259 /// <returns><c>true</c> if a new token was received successfully.</retu
rns> | 131 /// <returns><c>true</c> if a new token was received successfully.</retu
rns> |
260 public async Task<bool> RequestAccessTokenAsync(CancellationToken taskCa
ncellationToken) | 132 public override async Task<bool> RequestAccessTokenAsync(CancellationTok
en taskCancellationToken) |
261 { | 133 { |
262 string serializedHeader = CreateSerializedHeader(); | 134 string serializedHeader = CreateSerializedHeader(); |
263 string serializedPayload = GetSerializedPayload(); | 135 string serializedPayload = GetSerializedPayload(); |
264 | 136 |
265 StringBuilder assertion = new StringBuilder(); | 137 StringBuilder assertion = new StringBuilder(); |
266 assertion.Append(UrlSafeBase64Encode(serializedHeader)) | 138 assertion.Append(UrlSafeBase64Encode(serializedHeader)) |
267 .Append(".") | 139 .Append(".") |
268 .Append(UrlSafeBase64Encode(serializedPayload)); | 140 .Append(UrlSafeBase64Encode(serializedPayload)); |
269 | 141 |
270 // Sign the header and the payload. | 142 // Sign the header and the payload. |
271 var signature = UrlSafeBase64Encode(key.SignData(Encoding.ASCII.GetB
ytes(assertion.ToString()), "SHA256")); | 143 var signature = UrlSafeBase64Encode(key.SignData(Encoding.ASCII.GetB
ytes(assertion.ToString()), "SHA256")); |
272 assertion.Append(".").Append(signature); | 144 assertion.Append(".").Append(signature); |
273 | 145 |
274 // Create the request. | 146 // Create the request. |
275 var request = new GoogleAssertionTokenRequest() | 147 var request = new GoogleAssertionTokenRequest() |
276 { | 148 { |
277 Assertion = assertion.ToString() | 149 Assertion = assertion.ToString() |
278 }; | 150 }; |
279 | 151 |
280 Logger.Debug("Request a new access token. Assertion data is: " + req
uest.Assertion); | 152 Logger.Debug("Request a new access token. Assertion data is: " + req
uest.Assertion); |
281 | 153 |
282 var newToken = await request.ExecuteAsync(httpClient, tokenServerUrl
, taskCancellationToken, Clock) | 154 var newToken = await request.ExecuteAsync(HttpClient, TokenServerUrl
, taskCancellationToken, Clock) |
283 .ConfigureAwait(false); | 155 .ConfigureAwait(false); |
284 Token = newToken; | 156 Token = newToken; |
285 return true; | 157 return true; |
286 } | 158 } |
287 | 159 |
| 160 #endregion |
| 161 |
288 /// <summary> | 162 /// <summary> |
289 /// Creates a serialized header as specified in· | 163 /// Creates a serialized header as specified in· |
290 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#for
mingheader. | 164 /// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#for
mingheader. |
291 /// </summary> | 165 /// </summary> |
292 private static string CreateSerializedHeader() | 166 private static string CreateSerializedHeader() |
293 { | 167 { |
294 var header = new GoogleJsonWebSignature.Header() | 168 var header = new GoogleJsonWebSignature.Header() |
295 { | 169 { |
296 Algorithm = "RS256", | 170 Algorithm = "RS256", |
297 Type = "JWT" | 171 Type = "JWT" |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
330 | 204 |
331 /// <summary>Encodes the byte array into an URL safe base64 string.</sum
mary> | 205 /// <summary>Encodes the byte array into an URL safe base64 string.</sum
mary> |
332 /// <param name="bytes">Byte array to encode.</param> | 206 /// <param name="bytes">Byte array to encode.</param> |
333 /// <returns>The URL safe base64 string.</returns> | 207 /// <returns>The URL safe base64 string.</returns> |
334 private string UrlSafeBase64Encode(byte[] bytes) | 208 private string UrlSafeBase64Encode(byte[] bytes) |
335 { | 209 { |
336 return Convert.ToBase64String(bytes).Replace("=", String.Empty).Repl
ace('+', '-').Replace('/', '_'); | 210 return Convert.ToBase64String(bytes).Replace("=", String.Empty).Repl
ace('+', '-').Replace('/', '_'); |
337 } | 211 } |
338 } | 212 } |
339 } | 213 } |
OLD | NEW |