Skip to content

Commit d8ed4f2

Browse files
committed
refresh token
1 parent 42e494b commit d8ed4f2

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

src/shared/GitLab/GitLabAuthentication.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface IGitLabAuthentication : IDisposable
1616
AuthenticationPromptResult GetAuthentication(Uri targetUri, string userName, AuthenticationModes modes);
1717

1818
Task<OAuth2TokenResult> GetOAuthTokenViaBrowserAsync(Uri targetUri, IEnumerable<string> scopes);
19+
20+
Task<OAuth2TokenResult> GetOAuthTokenViaRefresh(Uri targetUri, string refreshToken);
1921
}
2022

2123
public class AuthenticationPromptResult
@@ -141,6 +143,12 @@ public async Task<OAuth2TokenResult> GetOAuthTokenViaBrowserAsync(Uri targetUri,
141143
return await oauthClient.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None);
142144
}
143145

146+
public async Task<OAuth2TokenResult> GetOAuthTokenViaRefresh(Uri targetUri, string refreshToken)
147+
{
148+
var oauthClient = new GitLabOAuth2Client(HttpClient, Context.Settings, targetUri);
149+
return await oauthClient.GetTokenByRefreshTokenAsync(refreshToken, CancellationToken.None);
150+
}
151+
144152
private HttpClient _httpClient;
145153
private HttpClient HttpClient => _httpClient ?? (_httpClient = Context.HttpClientFactory.CreateClient());
146154

src/shared/GitLab/GitLabHostProvider.cs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public override async Task<ICredential> GenerateCredentialAsync(InputArguments i
116116
return promptResult.Credential;
117117

118118
case AuthenticationModes.Browser:
119-
return await GenerateOAuthCredentialAsync(remoteUri);
119+
return await GenerateOAuthCredentialAsync(input);
120120

121121
default:
122122
throw new ArgumentOutOfRangeException(nameof(promptResult));
@@ -152,18 +152,75 @@ internal AuthenticationModes GetSupportedAuthenticationModes(Uri targetUri)
152152
return AuthenticationModes.Basic | AuthenticationModes.Pat;
153153
}
154154

155-
private async Task<GitCredential> GenerateOAuthCredentialAsync(Uri targetUri)
155+
public override async Task<ICredential> GetCredentialAsync(InputArguments input)
156156
{
157-
OAuth2TokenResult result = await _gitLabAuth.GetOAuthTokenViaBrowserAsync(targetUri, GitLabOAuthScopes);
157+
ICredential credential = await base.GetCredentialAsync(input);
158+
if (credential.Account == "oauth2")
159+
{
160+
// cast succeeds if and only if credential is freshly generated (not retrieved)
161+
OAuthCredential oAuthCredential = credential as OAuthCredential;
162+
if (oAuthCredential == null)
163+
{
164+
// retrieved OAuth credential may have expired, so refresh
165+
try
166+
{
167+
oAuthCredential = await RefreshOAuthCredentialAsync(input);
168+
}
169+
catch (Exception e)
170+
{
171+
Context.Terminal.WriteLine($"OAuth token refresh failed: {e.Message}");
172+
return credential;
173+
}
174+
}
175+
// store refresh token under a separate service
176+
Context.Trace.WriteLine("Storing refresh token...");
177+
Context.CredentialStore.AddOrUpdate(GetRefreshTokenServiceName(input.GetRemoteUri()), "oauth2", oAuthCredential.RefreshToken);
178+
return oAuthCredential;
179+
}
158180

159-
// username oauth2 https://gitlab.com/gitlab-org/gitlab/-/issues/349461
160-
return new GitCredential("oauth2", result.AccessToken);
181+
return credential;
182+
}
183+
184+
internal class OAuthCredential : GitCredential
185+
{
186+
// username must be oauth2 https://gitlab.com/gitlab-org/gitlab/-/issues/349461
187+
public OAuthCredential(OAuth2TokenResult oAuth2TokenResult) : base("oauth2", oAuth2TokenResult.AccessToken)
188+
{
189+
RefreshToken = oAuth2TokenResult.RefreshToken;
190+
}
191+
192+
public string RefreshToken { get; }
193+
}
194+
195+
private async Task<OAuthCredential> GenerateOAuthCredentialAsync(InputArguments input)
196+
{
197+
OAuth2TokenResult result = await _gitLabAuth.GetOAuthTokenViaBrowserAsync(input.GetRemoteUri(), GitLabOAuthScopes);
198+
return new OAuthCredential(result);
199+
}
200+
201+
private async Task<OAuthCredential> RefreshOAuthCredentialAsync(InputArguments input)
202+
{
203+
// retrieve refresh token stored under separate service
204+
Context.Trace.WriteLine($"Checking for stored refresh token...");
205+
string refreshTokenServiceName = GetRefreshTokenServiceName(input.GetRemoteUri());
206+
string refreshToken = Context.CredentialStore.Get(refreshTokenServiceName, "oauth2").Password;
207+
if (refreshToken == null)
208+
{
209+
throw new InvalidOperationException("No stored refresh token");
210+
}
211+
OAuth2TokenResult result = await _gitLabAuth.GetOAuthTokenViaRefresh(input.GetRemoteUri(), refreshToken);
212+
return new OAuthCredential(result);
161213
}
162214

163215
protected override void ReleaseManagedResources()
164216
{
165217
_gitLabAuth.Dispose();
166218
base.ReleaseManagedResources();
167219
}
220+
221+
private static string GetRefreshTokenServiceName(Uri baseUri)
222+
{
223+
return new Uri(baseUri.WithoutUserInfo(), "/refresh_token").AbsoluteUri;
224+
}
168225
}
169226
}

0 commit comments

Comments
 (0)