LEFT | RIGHT |
(no file at all) | |
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. | |
15 */ | |
16 | |
17 using System; | |
18 using System.Net; | |
19 using System.Net.Http; | |
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; | |
26 | |
27 namespace Google.Apis.Auth.OAuth2 | |
28 { | |
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 | |
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 } | |
138 } | |
139 } | |
LEFT | RIGHT |