osu/osu.Game/Online/API/OAuth.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

221 lines
6.9 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
using System;
2016-08-31 10:49:34 +00:00
using System.Diagnostics;
using System.Net.Http;
using Newtonsoft.Json;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
namespace osu.Game.Online.API
{
public class OAuth
2016-08-31 10:49:34 +00:00
{
private readonly string clientId;
private readonly string clientSecret;
private readonly string endpoint;
2018-04-13 09:19:50 +00:00
public readonly Bindable<OAuthToken> Token = new Bindable<OAuthToken>();
2018-04-13 09:19:50 +00:00
2018-04-12 05:30:28 +00:00
public string TokenString
{
get => Token.Value?.ToString();
set => Token.Value = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value);
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal OAuth(string clientId, string clientSecret, string endpoint)
{
Debug.Assert(clientId != null);
Debug.Assert(clientSecret != null);
Debug.Assert(endpoint != null);
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
this.clientId = clientId;
this.clientSecret = clientSecret;
this.endpoint = endpoint;
}
2018-04-13 09:19:50 +00:00
internal void AuthenticateWithLogin(string username, string password)
2016-08-31 10:49:34 +00:00
{
if (string.IsNullOrEmpty(username)) throw new ArgumentException("Missing username.");
if (string.IsNullOrEmpty(password)) throw new ArgumentException("Missing password.");
2018-04-13 09:19:50 +00:00
var accessTokenRequest = new AccessTokenRequestPassword(username, password)
{
Url = $@"{endpoint}/oauth/token",
Method = HttpMethod.Post,
ClientId = clientId,
ClientSecret = clientSecret
};
using (accessTokenRequest)
2016-08-31 10:49:34 +00:00
{
try
{
accessTokenRequest.Perform();
}
catch (Exception ex)
{
Token.Value = null;
var throwableException = ex;
try
{
// attempt to decode a displayable error string.
var error = JsonConvert.DeserializeObject<OAuthError>(accessTokenRequest.GetResponseString() ?? string.Empty);
if (error != null)
throwableException = new APIException(error.UserDisplayableError, ex);
}
catch
{
}
throw throwableException;
}
2018-04-13 09:19:50 +00:00
Token.Value = accessTokenRequest.ResponseObject;
}
2016-08-31 10:49:34 +00:00
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal bool AuthenticateWithRefresh(string refresh)
{
try
{
var refreshRequest = new AccessTokenRequestRefresh(refresh)
{
Url = $@"{endpoint}/oauth/token",
Method = HttpMethod.Post,
ClientId = clientId,
ClientSecret = clientSecret
};
using (refreshRequest)
{
refreshRequest.Perform();
2018-04-13 09:19:50 +00:00
Token.Value = refreshRequest.ResponseObject;
return true;
}
2016-08-31 10:49:34 +00:00
}
2016-09-21 08:37:33 +00:00
catch
2016-08-31 10:49:34 +00:00
{
//todo: potentially only kill the refresh token on certain exception types.
Token.Value = null;
2016-08-31 10:49:34 +00:00
return false;
}
}
2018-04-13 09:19:50 +00:00
private static readonly object access_token_retrieval_lock = new object();
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
/// <summary>
/// Should be run before any API request to make sure we have a valid key.
/// </summary>
private bool ensureAccessToken()
{
// if we already have a valid access token, let's use it.
2016-08-31 10:49:34 +00:00
if (accessTokenValid) return true;
2018-04-13 09:19:50 +00:00
// we want to ensure only a single authentication update is happening at once.
lock (access_token_retrieval_lock)
{
2017-05-11 10:49:28 +00:00
// re-check if valid, in case another request completed and revalidated our access.
if (accessTokenValid) return true;
2018-04-13 09:19:50 +00:00
// if not, let's try using our refresh token to request a new access token.
if (!string.IsNullOrEmpty(Token.Value?.RefreshToken))
// ReSharper disable once PossibleNullReferenceException
AuthenticateWithRefresh(Token.Value.RefreshToken);
2018-04-13 09:19:50 +00:00
return accessTokenValid;
}
2016-08-31 10:49:34 +00:00
}
2018-04-13 09:19:50 +00:00
private bool accessTokenValid => Token.Value?.IsValid ?? false;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal bool HasValidAccessToken => RequestAccessToken() != null;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal string RequestAccessToken()
{
if (!ensureAccessToken()) return null;
2018-04-13 09:19:50 +00:00
return Token.Value.AccessToken;
2016-08-31 10:49:34 +00:00
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal void Clear()
{
Token.Value = null;
2016-08-31 10:49:34 +00:00
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
private class AccessTokenRequestRefresh : AccessTokenRequest
{
internal readonly string RefreshToken;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal AccessTokenRequestRefresh(string refreshToken)
{
RefreshToken = refreshToken;
GrantType = @"refresh_token";
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
protected override void PrePerform()
{
AddParameter("refresh_token", RefreshToken);
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
base.PrePerform();
}
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
private class AccessTokenRequestPassword : AccessTokenRequest
{
internal readonly string Username;
internal readonly string Password;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal AccessTokenRequestPassword(string username, string password)
{
Username = username;
Password = password;
GrantType = @"password";
}
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
protected override void PrePerform()
{
AddParameter("username", Username);
AddParameter("password", Password);
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
base.PrePerform();
}
}
2018-04-13 09:19:50 +00:00
private class AccessTokenRequest : OsuJsonWebRequest<OAuthToken>
2016-08-31 10:49:34 +00:00
{
protected string GrantType;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
internal string ClientId;
internal string ClientSecret;
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
protected override void PrePerform()
{
AddParameter("grant_type", GrantType);
AddParameter("client_id", ClientId);
AddParameter("client_secret", ClientSecret);
2018-11-28 10:02:23 +00:00
AddParameter("scope", "*");
2018-04-13 09:19:50 +00:00
2016-08-31 10:49:34 +00:00
base.PrePerform();
}
}
private class OAuthError
{
public string UserDisplayableError => !string.IsNullOrEmpty(Hint) ? Hint : ErrorIdentifier;
[JsonProperty("error")]
public string ErrorIdentifier { get; set; }
[JsonProperty("hint")]
public string Hint { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
2016-08-31 10:49:34 +00:00
}
}