2020-02-18 13:48:38 +00:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* (c) Copyright Ascensio System Limited 2010-2018
|
|
|
|
*
|
|
|
|
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
|
|
|
|
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
|
|
|
|
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
|
|
|
|
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
|
|
|
|
*
|
|
|
|
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
|
|
|
|
*
|
|
|
|
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
|
|
|
|
*
|
|
|
|
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
|
|
|
|
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
|
|
|
|
*
|
|
|
|
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
|
|
|
|
* relevant author attributions when distributing the software. If the display of the logo in its graphic
|
|
|
|
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
|
|
|
|
* in every copy of the program you distribute.
|
|
|
|
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Globalization;
|
2020-02-25 11:43:01 +00:00
|
|
|
using System.Linq;
|
2020-02-18 13:48:38 +00:00
|
|
|
using System.Net;
|
2020-02-25 11:43:01 +00:00
|
|
|
using System.Security.Authentication;
|
2020-10-23 07:42:28 +00:00
|
|
|
using System.Security.Claims;
|
2020-02-18 13:48:38 +00:00
|
|
|
using System.Security.Cryptography;
|
|
|
|
using System.Text;
|
2020-02-25 11:43:01 +00:00
|
|
|
using System.Text.Encodings.Web;
|
2020-10-19 15:53:15 +00:00
|
|
|
using System.Threading.Tasks;
|
2020-05-19 11:45:24 +00:00
|
|
|
|
2020-10-29 14:30:08 +00:00
|
|
|
using ASC.Common;
|
2020-05-19 11:45:24 +00:00
|
|
|
using ASC.Common.Logging;
|
2020-10-23 07:42:28 +00:00
|
|
|
using ASC.Core.Common.Security;
|
2020-09-18 07:59:23 +00:00
|
|
|
using ASC.Security.Cryptography;
|
2020-05-19 11:45:24 +00:00
|
|
|
using ASC.Web.Core.Helpers;
|
|
|
|
|
|
|
|
using Microsoft.AspNetCore.Authentication;
|
2020-10-23 07:42:28 +00:00
|
|
|
using Microsoft.AspNetCore.Http;
|
2020-05-19 11:45:24 +00:00
|
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using Microsoft.Extensions.Options;
|
2020-02-18 13:48:38 +00:00
|
|
|
|
|
|
|
namespace ASC.ApiSystem.Classes
|
2020-10-29 14:30:08 +00:00
|
|
|
{
|
|
|
|
[Scope]
|
2020-02-25 11:43:01 +00:00
|
|
|
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
2020-02-25 11:43:01 +00:00
|
|
|
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
|
|
|
|
base(options, logger, encoder, clock)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
public AuthHandler(
|
|
|
|
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
|
|
|
ILoggerFactory logger,
|
|
|
|
UrlEncoder encoder,
|
|
|
|
ISystemClock clock,
|
|
|
|
IConfiguration configuration,
|
|
|
|
IOptionsMonitor<ILog> option,
|
2020-09-18 07:59:23 +00:00
|
|
|
ApiSystemHelper apiSystemHelper,
|
2020-10-23 07:42:28 +00:00
|
|
|
MachinePseudoKeys machinePseudoKeys,
|
|
|
|
IHttpContextAccessor httpContextAccessor) :
|
2020-02-25 11:43:01 +00:00
|
|
|
base(options, logger, encoder, clock)
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
|
|
|
Configuration = configuration;
|
|
|
|
|
|
|
|
Log = option.Get("ASC.ApiSystem");
|
|
|
|
|
2020-09-18 07:59:23 +00:00
|
|
|
ApiSystemHelper = apiSystemHelper;
|
|
|
|
MachinePseudoKeys = machinePseudoKeys;
|
2020-10-23 07:42:28 +00:00
|
|
|
HttpContextAccessor = httpContextAccessor;
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
private ILog Log { get; }
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
private IConfiguration Configuration { get; }
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-09-18 07:59:23 +00:00
|
|
|
private ApiSystemHelper ApiSystemHelper { get; }
|
|
|
|
private MachinePseudoKeys MachinePseudoKeys { get; }
|
2020-10-23 07:42:28 +00:00
|
|
|
private IHttpContextAccessor HttpContextAccessor { get; }
|
2020-09-18 07:59:23 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
|
|
{
|
|
|
|
if (Convert.ToBoolean(Configuration[Scheme.Name] ?? "false"))
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
2020-02-25 11:43:01 +00:00
|
|
|
Log.DebugFormat("Auth for {0} skipped", Scheme.Name);
|
|
|
|
|
|
|
|
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2020-02-25 11:43:01 +00:00
|
|
|
Context.Request.Headers.TryGetValue("Authorization", out var headers);
|
|
|
|
|
|
|
|
var header = headers.FirstOrDefault();
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
if (string.IsNullOrEmpty(header))
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
|
|
|
Log.Debug("Auth header is NULL");
|
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Unauthorized.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
|
2020-02-25 12:06:16 +00:00
|
|
|
var substring = "ASC";
|
|
|
|
|
2020-10-23 07:42:28 +00:00
|
|
|
if (header.StartsWith(substring, StringComparison.InvariantCultureIgnoreCase))
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
2020-02-25 12:06:16 +00:00
|
|
|
var splitted = header.Substring(substring.Length).Trim().Split(':', StringSplitOptions.RemoveEmptyEntries);
|
2020-02-18 13:48:38 +00:00
|
|
|
|
|
|
|
if (splitted.Length < 3)
|
|
|
|
{
|
2020-02-25 11:43:01 +00:00
|
|
|
Log.DebugFormat("Auth failed: invalid token {0}.", header);
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Unauthorized.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var pkey = splitted[0];
|
|
|
|
var date = splitted[1];
|
|
|
|
var orighash = splitted[2];
|
|
|
|
|
|
|
|
Log.Debug("Variant of correct auth:" + ApiSystemHelper.CreateAuthToken(pkey));
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(date))
|
|
|
|
{
|
|
|
|
var timestamp = DateTime.ParseExact(date, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
|
|
|
|
|
2020-05-19 13:22:47 +00:00
|
|
|
var trustInterval = TimeSpan.FromMinutes(Convert.ToDouble(Configuration["auth:trust-interval"] ?? "5"));
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
if (DateTime.UtcNow > timestamp.Add(trustInterval))
|
2020-02-18 13:48:38 +00:00
|
|
|
{
|
|
|
|
Log.DebugFormat("Auth failed: invalid timesatmp {0}, now {1}.", timestamp, DateTime.UtcNow);
|
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-18 07:59:23 +00:00
|
|
|
var skey = MachinePseudoKeys.GetMachineConstant();
|
|
|
|
using var hasher = new HMACSHA1(skey);
|
2020-02-18 13:48:38 +00:00
|
|
|
var data = string.Join("\n", date, pkey);
|
|
|
|
var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(data));
|
|
|
|
|
|
|
|
if (WebEncoders.Base64UrlEncode(hash) != orighash && Convert.ToBase64String(hash) != orighash)
|
|
|
|
{
|
|
|
|
Log.DebugFormat("Auth failed: invalid token {0}, expect {1} or {2}.", orighash, WebEncoders.Base64UrlEncode(hash), Convert.ToBase64String(hash));
|
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-02-25 11:43:01 +00:00
|
|
|
Log.DebugFormat("Auth failed: invalid auth header. Sheme: {0}, parameter: {1}.", Scheme.Name, header);
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
Log.Error(ex);
|
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.InternalServerError.ToString())));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
2020-10-23 07:42:28 +00:00
|
|
|
var identity = new ClaimsIdentity( Scheme.Name);
|
2020-02-18 13:48:38 +00:00
|
|
|
|
2020-02-25 11:43:01 +00:00
|
|
|
Log.InfoFormat("Auth success {0}", Scheme.Name);
|
2020-10-23 07:42:28 +00:00
|
|
|
if (HttpContextAccessor?.HttpContext != null) HttpContextAccessor.HttpContext.User = new CustomClaimsPrincipal(new ClaimsIdentity(Scheme.Name), identity);
|
2020-02-25 11:43:01 +00:00
|
|
|
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
|
2020-02-18 13:48:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|