LEFT | RIGHT |
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 |
(...skipping 17 matching lines...) Expand all Loading... |
28 { | 28 { |
29 /// <summary> | 29 /// <summary> |
30 /// OAuth 2.0 credential for accessing protected resources using an access t
oken, as well as optionally refreshing· | 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. | 31 /// the access token when it expires using a refresh token. |
32 /// </summary> | 32 /// </summary> |
33 public class Credential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponse
Handler, | 33 public class Credential : IHttpExecuteInterceptor, IHttpUnsuccessfulResponse
Handler, |
34 IConfigurableHttpClientInitializer | 34 IConfigurableHttpClientInitializer |
35 { | 35 { |
36 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<Credential>(); | 36 private static readonly ILogger Logger = ApplicationContext.Logger.ForTy
pe<Credential>(); |
37 | 37 |
38 private readonly TokenResponse token; | 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 |
39 private readonly IAuthorizationCodeFlow flow; | 59 private readonly IAuthorizationCodeFlow flow; |
40 private readonly string userId; | 60 private readonly string userId; |
41 | 61 |
42 /// <summary>Constructs a new credential instance.</summary> | 62 /// <summary>Constructs a new credential instance.</summary> |
43 /// <param name="flow">Authorization code flow</param> | 63 /// <param name="flow">Authorization code flow</param> |
44 /// <param name="userId">User identifier</param> | 64 /// <param name="userId">User identifier</param> |
45 /// <param name="token">An initial token for the user</param> | 65 /// <param name="token">An initial token for the user</param> |
46 public Credential(IAuthorizationCodeFlow flow, string userId, TokenRespo
nse token) | 66 public Credential(IAuthorizationCodeFlow flow, string userId, TokenRespo
nse token) |
47 { | 67 { |
48 this.flow = flow; | 68 this.flow = flow; |
| 69 this.userId = userId; |
49 this.token = token; | 70 this.token = token; |
50 this.userId = userId; | |
51 } | 71 } |
52 | 72 |
53 /// <summary> | 73 /// <summary> |
54 /// Default implementation is to try to refresh the access token if ther
e is no access token or if we are 1· | 74 /// Default implementation is to try to refresh the access token if ther
e is no access token or if we are 1· |
55 /// minute away from expiration. If token server is unavailable, it will
try to use the access token even if· | 75 /// minute away from expiration. If token server is unavailable, it will
try to use the access token even if· |
56 /// has expired. If successful, it will call <seealso cref="IAccessMetho
d.Intercept"/>. | 76 /// has expired. If successful, it will call <seealso cref="IAccessMetho
d.Intercept"/>. |
57 /// </summary> | 77 /// </summary> |
58 public async Task InterceptAsync(HttpRequestMessage request, Cancellatio
nToken taskCancellationToken) | 78 public async Task InterceptAsync(HttpRequestMessage request, Cancellatio
nToken taskCancellationToken) |
59 { | 79 { |
60 if (token.IsExpired(flow.Clock)) | 80 if (Token.IsExpired(flow.Clock)) |
61 { | 81 { |
62 if (!await RefreshTokenAsync(taskCancellationToken).ConfigureAwa
it(false)) | 82 if (!await RefreshTokenAsync(taskCancellationToken).ConfigureAwa
it(false)) |
63 { | 83 { |
64 throw new InvalidOperationException("The access token is exp
ired but we can't refresh it"); | 84 throw new InvalidOperationException("The access token is exp
ired but we can't refresh it"); |
65 } | 85 } |
66 } | 86 } |
67 | 87 |
68 flow.AccessMethod.Intercept(request, token.AccessToken); | 88 flow.AccessMethod.Intercept(request, Token.AccessToken); |
69 } | 89 } |
70 | 90 |
71 /// <summary> | 91 /// <summary> |
72 /// Refreshes the token by calling to <seealso cref="IAuthorizationCodeF
low.RefreshTokenAsync"/>. Then it· | 92 /// Refreshes the token by calling to <seealso cref="IAuthorizationCodeF
low.RefreshTokenAsync"/>. Then it· |
73 /// updates the <see cref="TokenResponse"/> with the new token instance. | 93 /// updates the <see cref="TokenResponse"/> with the new token instance. |
74 /// </summary> | 94 /// </summary> |
75 /// <param name="taskCancellationToken">Cancellation token to cancel an
operation</param> | 95 /// <param name="taskCancellationToken">Cancellation token to cancel an
operation</param> |
76 /// <returns><c>true</c> if the token was refreshed</returns> | 96 /// <returns><c>true</c> if the token was refreshed</returns> |
77 private async Task<bool> RefreshTokenAsync(CancellationToken taskCancell
ationToken) | 97 private async Task<bool> RefreshTokenAsync(CancellationToken taskCancell
ationToken) |
78 { | 98 { |
79 if (token.RefreshToken == null) | 99 if (Token.RefreshToken == null) |
80 { | 100 { |
81 Logger.Warning("Refresh token is null, can't refresh the token!"
); | 101 Logger.Warning("Refresh token is null, can't refresh the token!"
); |
82 return false; | 102 return false; |
83 } | 103 } |
84 | 104 |
85 // It's possible that two concurrent calls will be made to refresh t
he token, in that case the last one· | 105 // It's possible that two concurrent calls will be made to refresh t
he token, in that case the last one· |
86 // will win. | 106 // will win. |
87 var newToken = await flow.RefreshTokenAsync(userId, token.RefreshTok
en, taskCancellationToken) | 107 var newToken = await flow.RefreshTokenAsync(userId, Token.RefreshTok
en, taskCancellationToken) |
88 .ConfigureAwait(false); | 108 .ConfigureAwait(false); |
89 | 109 |
90 Logger.Info("Access token was refreshed"); | 110 Logger.Info("Access token was refreshed"); |
91 | 111 |
92 token.CopyFrom(newToken); | 112 if (newToken.RefreshToken == null) |
| 113 { |
| 114 newToken.RefreshToken = Token.RefreshToken; |
| 115 } |
93 | 116 |
| 117 Token = newToken; |
94 return true; | 118 return true; |
95 } | 119 } |
96 | 120 |
97 public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseAr
gs args) | 121 public async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseAr
gs args) |
98 { | 122 { |
99 // TODO(peleyal): check WWW-Authenticate header | 123 // TODO(peleyal): check WWW-Authenticate header |
100 if (args.Response.StatusCode == HttpStatusCode.Unauthorized) | 124 if (args.Response.StatusCode == HttpStatusCode.Unauthorized) |
101 { | 125 { |
102 return !Object.Equals(token.AccessToken, flow.AccessMethod.GetAc
cessToken(args.Request)) | 126 return !Object.Equals(Token.AccessToken, flow.AccessMethod.GetAc
cessToken(args.Request)) |
103 || await RefreshTokenAsync(args.CancellationToken).Configure
Await(false); | 127 || await RefreshTokenAsync(args.CancellationToken).Configure
Await(false); |
104 } | 128 } |
105 | 129 |
106 return false; | 130 return false; |
107 } | 131 } |
108 | 132 |
109 public void Initialize(ConfigurableHttpClient httpClient) | 133 public void Initialize(ConfigurableHttpClient httpClient) |
110 { | 134 { |
111 httpClient.MessageHandler.ExecuteInterceptors.Add(this); | 135 httpClient.MessageHandler.ExecuteInterceptors.Add(this); |
112 httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); | 136 httpClient.MessageHandler.UnsuccessfulResponseHandlers.Add(this); |
113 } | 137 } |
114 } | 138 } |
115 } | 139 } |
LEFT | RIGHT |