LEFT | RIGHT |
| 1 /* |
| 2 Copyright 2013 Google Inc |
| 3 |
| 4 Licensed under the Apache License, Version 2.0 (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 |
| 7 |
| 8 http://www.apache.org/licenses/LICENSE-2.0 |
| 9 |
| 10 Unless required by applicable law or agreed to in writing, software |
| 11 distributed under the License is distributed on an "AS IS" BASIS, |
| 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 See the License for the specific language governing permissions and |
| 14 limitations under the License. |
1 */ | 15 */ |
2 | 16 |
3 using System; | 17 using System; |
4 using System.Collections.Generic; | 18 using System.Net; |
5 using System.Linq; | 19 using System.Net.Http; |
6 using System.Text; | 20 using System.Threading; |
| 21 using System.Threading.Tasks; |
| 22 |
| 23 using Google.Apis.Auth.OAuth2.Responses; |
| 24 using Google.Apis.Http; |
| 25 using Google.Apis.Logging; |
7 | 26 |
8 namespace Google.Apis.Auth.OAuth2 | 27 namespace Google.Apis.Auth.OAuth2 |
9 { | 28 { |
10 public class Credential | 29 /// <summary> |
| 30 /// OAuth 2.0 credential for accessing protected resources using an access t
oken, as well as optionally refreshing· |
| 31 /// the access token when it expires using a refresh token. |
| 32 /// </summary> |
| 33 public class Credential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponse
Handler, |
| 34 IConfigurableHttpClientInitializer |
11 { | 35 { |
| 36 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<Credential>(); |
| 37 |
| 38 private TokenResponse token; |
| 39 private object lockObject = new object(); |
| 40 |
| 41 public TokenResponse Token |
| 42 { |
| 43 get |
| 44 { |
| 45 lock (lockObject) |
| 46 { |
| 47 return token; |
| 48 } |
| 49 } |
| 50 private set |
| 51 { |
| 52 lock (lockObject) |
| 53 { |
| 54 token = value; |
| 55 } |
| 56 } |
| 57 } |
| 58 |
| 59 private readonly IAuthorizationCodeFlow flow; |
| 60 private readonly string userId; |
| 61 |
| 62 /// <summary>Constructs a new credential instance.</summary> |
| 63 /// <param name="flow">Authorization code flow</param> |
| 64 /// <param name="userId">User identifier</param> |
| 65 /// <param name="token">An initial token for the user</param> |
| 66 public Credential(IAuthorizationCodeFlow flow, string userId, TokenRespo
nse token) |
| 67 { |
| 68 this.flow = flow; |
| 69 this.userId = userId; |
| 70 this.token = token; |
| 71 } |
| 72 |
| 73 /// <summary> |
| 74 /// Default implementation is to try to refresh the access token if ther
e is no access token or if we are 1· |
| 75 /// minute away from expiration. If token server is unavailable, it will
try to use the access token even if· |
| 76 /// has expired. If successful, it will call <seealso cref="IAccessMetho
d.Intercept"/>. |
| 77 /// </summary> |
| 78 public async Task InterceptAsync(HttpRequestMessage request, Cancellatio
nToken taskCancellationToken) |
| 79 { |
| 80 if (Token.IsExpired(flow.Clock)) |
| 81 { |
| 82 if (!await RefreshTokenAsync(taskCancellationToken).ConfigureAwa
it(false)) |
| 83 { |
| 84 throw new InvalidOperationException("The access token is exp
ired but we can't refresh it"); |
| 85 } |
| 86 } |
| 87 |
| 88 flow.AccessMethod.Intercept(request, Token.AccessToken); |
| 89 } |
| 90 |
| 91 /// <summary> |
| 92 /// Refreshes the token by calling to <seealso cref="IAuthorizationCodeF
low.RefreshTokenAsync"/>. Then it· |
| 93 /// updates the <see cref="TokenResponse"/> with the new token instance. |
| 94 /// </summary> |
| 95 /// <param name="taskCancellationToken">Cancellation token to cancel an
operation</param> |
| 96 /// <returns><c>true</c> if the token was refreshed</returns> |
| 97 private async Task<bool> RefreshTokenAsync(CancellationToken taskCancell
ationToken) |
| 98 { |
| 99 if (Token.RefreshToken == null) |
| 100 { |
| 101 Logger.Warning("Refresh token is null, can't refresh the token!"
); |
| 102 return false; |
| 103 } |
| 104 |
| 105 // It's possible that two concurrent calls will be made to refresh t
he token, in that case the last one· |
| 106 // will win. |
| 107 var newToken = await flow.RefreshTokenAsync(userId, Token.RefreshTok
en, taskCancellationToken) |
| 108 .ConfigureAwait(false); |
| 109 |
| 110 Logger.Info("Access token was refreshed"); |
| 111 |
| 112 if (newToken.RefreshToken == null) |
| 113 { |
| 114 newToken.RefreshToken = Token.RefreshToken; |
| 115 } |
| 116 |
| 117 Token = newToken; |
| 118 return true; |
| 119 } |
| 120 |
| 121 public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseAr
gs args) |
| 122 { |
| 123 // TODO(peleyal): check WWW-Authenticate header |
| 124 if (args.Response.StatusCode == HttpStatusCode.Unauthorized) |
| 125 { |
| 126 return !Object.Equals(Token.AccessToken, flow.AccessMethod.GetAc
cessToken(args.Request)) |
| 127 || await RefreshTokenAsync(args.CancellationToken).Configure
Await(false); |
| 128 } |
| 129 |
| 130 return false; |
| 131 } |
| 132 |
| 133 public void Initialize(ConfigurableHttpClient httpClient) |
| 134 { |
| 135 httpClient.MessageHandler.ExecuteInterceptors.Add(this); |
| 136 httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); |
| 137 } |
12 } | 138 } |
13 } | 139 } |
LEFT | RIGHT |