petrsvihlik/WopiHost

View on GitHub
src/WopiHost.Core/Controllers/WopiBootstrapperController.cs

Summary

Maintainability
A
2 hrs
Test Coverage
using System.Net.Mime;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using WopiHost.Abstractions;
using WopiHost.Core.Models;
using WopiHost.Core.Results;

namespace WopiHost.Core.Controllers;

/// <summary>
/// Controller containing the bootstrap operation.
/// </summary>
/// <remarks>
/// Creates an instance of <see cref="WopiBootstrapperController"/>.
/// </remarks>
/// <param name="storageProvider">Storage provider instance for retrieving files and folders.</param>
/// <param name="securityHandler">Security handler instance for performing security-related operations.</param>
/// <param name="wopiHostOptions">WOPI Host configuration</param>
[Route("wopibootstrapper")]
public class WopiBootstrapperController(IWopiStorageProvider storageProvider, IWopiSecurityHandler securityHandler, IOptionsSnapshot<WopiHostOptions> wopiHostOptions) : WopiControllerBase(storageProvider, securityHandler, wopiHostOptions)
{

    /// <summary>
    /// Gets information about the root container.
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    [Produces(MediaTypeNames.Application.Json)]
    public IActionResult GetRootContainer() //TODO: fix the path
    {
        var authorizationHeader = HttpContext.Request.Headers.Authorization;
        var ecosystemOperation = HttpContext.Request.Headers[WopiHeaders.ECOSYSTEM_OPERATION];
        var wopiSrc = HttpContext.Request.Headers[WopiHeaders.WOPI_SRC].FirstOrDefault();

        if (ValidateAuthorizationHeader(authorizationHeader))
        {
            //TODO: supply user
            var user = "Anonymous";

            //TODO: implement bootstrap
            var bootstrapRoot = new BootstrapRootContainerInfo
            {
                Bootstrap = new BootstrapInfo
                {
                    EcosystemUrl = GetWopiUrl("ecosystem", accessToken: "TODO"),
                    SignInName = "",
                    UserFriendlyName = "",
                    UserId = ""
                }
            };
            if (ecosystemOperation == "GET_ROOT_CONTAINER")
            {
                var resourceId = StorageProvider.RootContainerPointer.Identifier;
                var token = SecurityHandler.GenerateAccessToken(user, resourceId);

                bootstrapRoot.RootContainerInfo = new RootContainerInfo
                {
                    ContainerPointer = new ChildContainer
                    {
                        Name = StorageProvider.RootContainerPointer.Name,
                        Url = GetWopiUrl("containers", resourceId, SecurityHandler.WriteToken(token))
                    }
                };
            }
            else if (ecosystemOperation == "GET_NEW_ACCESS_TOKEN")
            {
                var token = SecurityHandler.GenerateAccessToken(user, GetIdFromUrl(wopiSrc));

                bootstrapRoot.AccessTokenInfo = new AccessTokenInfo
                {
                    AccessToken = SecurityHandler.WriteToken(token),
                    AccessTokenExpiry = token.ValidTo.ToUnixTimestamp()
                };
            }
            else
            {
                return new NotImplementedResult();
            }
            return new JsonResult(bootstrapRoot);
        }
        else
        {
            //TODO: implement WWW-authentication header https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/bootstrapper/bootstrap#www-authenticate-response-header-format
            var authorizationUri = "https://contoso.com/api/oauth2/authorize";
            var tokenIssuanceUri = "https://contoso.com/api/oauth2/token";
            var providerId = "tp_contoso";
            var urlSchemes = Uri.EscapeDataString("{\"iOS\" : [\"contoso\",\"contoso - EMM\"], \"Android\" : [\"contoso\",\"contoso - EMM\"], \"UWP\": [\"contoso\",\"contoso - EMM\"]}");
            Response.Headers.Append("WWW-Authenticate", $"Bearer authorization_uri=\"{authorizationUri}\",tokenIssuance_uri=\"{tokenIssuanceUri}\",providerId=\"{providerId}\", UrlSchemes=\"{urlSchemes}\"");
            return new UnauthorizedResult();
        }
    }

    private string GetIdFromUrl(string resourceUrl)
    {
        var resourceId = resourceUrl[(resourceUrl.LastIndexOf('/') + 1)..];
        var queryIndex = resourceId.IndexOf('?');
        if (queryIndex > -1)
        {
            resourceId = resourceId[..queryIndex];
        }
        resourceId = Uri.UnescapeDataString(resourceId);
        return resourceId;
    }

    private bool ValidateAuthorizationHeader(StringValues authorizationHeader)
    {
        //TODO: implement header validation https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/bootstrapper/getrootcontainer#sample-response
        // http://stackoverflow.com/questions/31948426/oauth-bearer-token-authentication-is-not-passing-signature-validation
        return true;
    }
}