2017-02-07 12:59:30 +08:00
|
|
|
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
|
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
2016-08-31 18:49:34 +08:00
|
|
|
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using osu.Framework.IO.Network;
|
|
|
|
|
|
|
|
|
|
namespace osu.Game.Online.API
|
|
|
|
|
{
|
|
|
|
|
internal class OAuth
|
|
|
|
|
{
|
|
|
|
|
private readonly string clientId;
|
|
|
|
|
private readonly string clientSecret;
|
|
|
|
|
private readonly string endpoint;
|
|
|
|
|
|
|
|
|
|
public OAuthToken Token;
|
|
|
|
|
|
|
|
|
|
internal OAuth(string clientId, string clientSecret, string endpoint)
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(clientId != null);
|
|
|
|
|
Debug.Assert(clientSecret != null);
|
|
|
|
|
Debug.Assert(endpoint != null);
|
|
|
|
|
|
|
|
|
|
this.clientId = clientId;
|
|
|
|
|
this.clientSecret = clientSecret;
|
|
|
|
|
this.endpoint = endpoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool AuthenticateWithLogin(string username, string password)
|
|
|
|
|
{
|
|
|
|
|
var req = new AccessTokenRequestPassword(username, password)
|
|
|
|
|
{
|
2016-11-30 16:47:40 +08:00
|
|
|
|
Url = $@"{endpoint}/oauth/token",
|
2016-08-31 18:49:34 +08:00
|
|
|
|
Method = HttpMethod.POST,
|
|
|
|
|
ClientId = clientId,
|
|
|
|
|
ClientSecret = clientSecret
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
req.BlockingPerform();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Token = req.ResponseObject;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool AuthenticateWithRefresh(string refresh)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var req = new AccessTokenRequestRefresh(refresh)
|
|
|
|
|
{
|
2016-11-30 16:47:40 +08:00
|
|
|
|
Url = $@"{endpoint}/oauth/token",
|
2016-08-31 18:49:34 +08:00
|
|
|
|
Method = HttpMethod.POST,
|
|
|
|
|
ClientId = clientId,
|
|
|
|
|
ClientSecret = clientSecret
|
|
|
|
|
};
|
|
|
|
|
req.BlockingPerform();
|
|
|
|
|
|
|
|
|
|
Token = req.ResponseObject;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-09-21 16:37:33 +08:00
|
|
|
|
catch
|
2016-08-31 18:49:34 +08:00
|
|
|
|
{
|
|
|
|
|
//todo: potentially only kill the refresh token on certain exception types.
|
|
|
|
|
Token = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-11 18:35:07 +08:00
|
|
|
|
private static readonly object access_token_retrieval_lock = new object();
|
|
|
|
|
|
2016-08-31 18:49:34 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Should be run before any API request to make sure we have a valid key.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool ensureAccessToken()
|
|
|
|
|
{
|
2017-05-11 18:35:07 +08:00
|
|
|
|
// if we already have a valid access token, let's use it.
|
2016-08-31 18:49:34 +08:00
|
|
|
|
if (accessTokenValid) return true;
|
|
|
|
|
|
2017-05-11 18:35:07 +08:00
|
|
|
|
// we want to ensure only a single authentication update is happening at once.
|
|
|
|
|
lock (access_token_retrieval_lock)
|
|
|
|
|
{
|
2017-05-11 18:49:28 +08:00
|
|
|
|
// re-check if valid, in case another request completed and revalidated our access.
|
2017-05-11 18:35:07 +08:00
|
|
|
|
if (accessTokenValid) return true;
|
2016-08-31 18:49:34 +08:00
|
|
|
|
|
2017-05-11 18:35:07 +08:00
|
|
|
|
// if not, let's try using our refresh token to request a new access token.
|
|
|
|
|
if (!string.IsNullOrEmpty(Token?.RefreshToken))
|
|
|
|
|
AuthenticateWithRefresh(Token.RefreshToken);
|
|
|
|
|
|
|
|
|
|
return accessTokenValid;
|
|
|
|
|
}
|
2016-08-31 18:49:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool accessTokenValid => Token?.IsValid ?? false;
|
|
|
|
|
|
|
|
|
|
internal bool HasValidAccessToken => RequestAccessToken() != null;
|
|
|
|
|
|
|
|
|
|
internal string RequestAccessToken()
|
|
|
|
|
{
|
|
|
|
|
if (!ensureAccessToken()) return null;
|
|
|
|
|
|
|
|
|
|
return Token.AccessToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Clear()
|
|
|
|
|
{
|
|
|
|
|
Token = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class AccessTokenRequestRefresh : AccessTokenRequest
|
|
|
|
|
{
|
|
|
|
|
internal readonly string RefreshToken;
|
|
|
|
|
|
|
|
|
|
internal AccessTokenRequestRefresh(string refreshToken)
|
|
|
|
|
{
|
|
|
|
|
RefreshToken = refreshToken;
|
|
|
|
|
GrantType = @"refresh_token";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void PrePerform()
|
|
|
|
|
{
|
|
|
|
|
Parameters[@"refresh_token"] = RefreshToken;
|
|
|
|
|
base.PrePerform();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class AccessTokenRequestPassword : AccessTokenRequest
|
|
|
|
|
{
|
|
|
|
|
internal readonly string Username;
|
|
|
|
|
internal readonly string Password;
|
|
|
|
|
|
|
|
|
|
internal AccessTokenRequestPassword(string username, string password)
|
|
|
|
|
{
|
|
|
|
|
Username = username;
|
|
|
|
|
Password = password;
|
|
|
|
|
GrantType = @"password";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void PrePerform()
|
|
|
|
|
{
|
|
|
|
|
Parameters[@"username"] = Username;
|
|
|
|
|
Parameters[@"password"] = Password;
|
|
|
|
|
base.PrePerform();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class AccessTokenRequest : JsonWebRequest<OAuthToken>
|
|
|
|
|
{
|
|
|
|
|
protected string GrantType;
|
|
|
|
|
|
|
|
|
|
internal string ClientId;
|
|
|
|
|
internal string ClientSecret;
|
|
|
|
|
|
|
|
|
|
protected override void PrePerform()
|
|
|
|
|
{
|
|
|
|
|
Parameters[@"grant_type"] = GrantType;
|
|
|
|
|
Parameters[@"client_id"] = ClientId;
|
|
|
|
|
Parameters[@"client_secret"] = ClientSecret;
|
|
|
|
|
base.PrePerform();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|