@@ -83,16 +83,6 @@ public override bool IsSupported(HttpResponseMessage response)
83
83
return response . Headers . Contains ( "X-Gitlab-Feature-Category" ) ;
84
84
}
85
85
86
- public override string GetServiceName ( InputArguments input )
87
- {
88
- var baseUri = new Uri ( base . GetServiceName ( input ) ) ;
89
-
90
- string url = baseUri . AbsoluteUri ;
91
-
92
- // Trim trailing slash
93
- return url . TrimEnd ( '/' ) ;
94
- }
95
-
96
86
public override async Task < ICredential > GenerateCredentialAsync ( InputArguments input )
97
87
{
98
88
ThrowIfDisposed ( ) ;
@@ -152,44 +142,46 @@ internal AuthenticationModes GetSupportedAuthenticationModes(Uri targetUri)
152
142
return AuthenticationModes . Basic | AuthenticationModes . Pat ;
153
143
}
154
144
145
+ // <remarks>Stores OAuth refresh token as a side effect</remarks>
155
146
public override async Task < ICredential > GetCredentialAsync ( InputArguments input )
156
147
{
157
148
ICredential credential = await base . GetCredentialAsync ( input ) ;
158
- if ( credential . Account == "oauth2" )
149
+ if ( credential . Account == "oauth2" && credential is not OAuthCredential )
159
150
{
160
- // cast succeeds if and only if credential is freshly generated (not retrieved)
161
- OAuthCredential oAuthCredential = credential as OAuthCredential ;
162
- if ( oAuthCredential == null )
151
+ Context . Trace . WriteLine ( "Retrieved stored OAuth credential" ) ;
152
+ // retrieved OAuth credential may have expired, so refresh
153
+ try
154
+ {
155
+ credential = await RefreshOAuthCredentialAsync ( input ) ;
156
+ }
157
+ catch ( Exception e )
163
158
{
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
- }
159
+ Context . Terminal . WriteLine ( $ "OAuth token refresh failed: { e . Message } ") ;
174
160
}
161
+ }
162
+ if ( credential is OAuthCredential oAuthCredential )
163
+ {
175
164
// 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 ;
165
+ string refreshTokenService = GetRefreshTokenServiceName ( input ) ;
166
+ Context . Trace . WriteLine ( $ "Storing credential with service= { refreshTokenService } account= { input . UserName } ..." ) ;
167
+ Context . CredentialStore . AddOrUpdate ( refreshTokenService , oAuthCredential . Account , oAuthCredential . RefreshToken ) ;
179
168
}
180
-
181
169
return credential ;
182
170
}
183
171
184
- internal class OAuthCredential : GitCredential
172
+ internal class OAuthCredential : ICredential
185
173
{
186
- // username must be oauth2 https://gitlab.com/gitlab-org/gitlab/-/issues/349461
187
- public OAuthCredential ( OAuth2TokenResult oAuth2TokenResult ) : base ( "oauth2" , oAuth2TokenResult . AccessToken )
174
+ public OAuthCredential ( OAuth2TokenResult oAuth2TokenResult )
188
175
{
176
+ AccessToken = oAuth2TokenResult . AccessToken ;
189
177
RefreshToken = oAuth2TokenResult . RefreshToken ;
190
178
}
191
179
180
+ // username must be 'oauth2' https://gitlab.com/gitlab-org/gitlab/-/issues/349461
181
+ public string Account => "oauth2" ;
182
+ public string AccessToken { get ; }
192
183
public string RefreshToken { get ; }
184
+ string ICredential . Password => AccessToken ;
193
185
}
194
186
195
187
private async Task < OAuthCredential > GenerateOAuthCredentialAsync ( InputArguments input )
@@ -202,7 +194,7 @@ private async Task<OAuthCredential> RefreshOAuthCredentialAsync(InputArguments i
202
194
{
203
195
// retrieve refresh token stored under separate service
204
196
Context . Trace . WriteLine ( $ "Checking for stored refresh token...") ;
205
- string refreshTokenServiceName = GetRefreshTokenServiceName ( input . GetRemoteUri ( ) ) ;
197
+ string refreshTokenServiceName = GetRefreshTokenServiceName ( input ) ;
206
198
string refreshToken = Context . CredentialStore . Get ( refreshTokenServiceName , "oauth2" ) . Password ;
207
199
if ( refreshToken == null )
208
200
{
@@ -218,9 +210,16 @@ protected override void ReleaseManagedResources()
218
210
base . ReleaseManagedResources ( ) ;
219
211
}
220
212
221
- private static string GetRefreshTokenServiceName ( Uri baseUri )
213
+ private string GetRefreshTokenServiceName ( InputArguments input )
214
+ {
215
+ return new Uri ( new Uri ( GetServiceName ( input ) ) , "/refresh_token" ) . AbsoluteUri ;
216
+ }
217
+
218
+ public override Task EraseCredentialAsync ( InputArguments input )
222
219
{
223
- return new Uri ( baseUri . WithoutUserInfo ( ) , "/refresh_token" ) . AbsoluteUri ;
220
+ Context . CredentialStore . Remove ( GetServiceName ( input ) , input . UserName ) ;
221
+ Context . CredentialStore . Remove ( GetRefreshTokenServiceName ( input ) , "oauth2" ) ;
222
+ return Task . CompletedTask ;
224
223
}
225
224
}
226
225
}
0 commit comments