preview service

This commit is contained in:
2022-03-07 17:13:10 +01:00
parent baa1f05416
commit a394091023
12 changed files with 173 additions and 13 deletions
@@ -0,0 +1,7 @@
import { post, ApiResponse, ApiRequestConfig } from '../../services/request';
export default {
createPreviewToken: (key: string, config?: ApiRequestConfig): Promise<ApiResponse<any>> => post('backoffice/ui/previews/token', { key }, { ...config }),
};
@@ -10,6 +10,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import api from './api';
export default defineComponent({
@@ -19,7 +20,11 @@
created()
{
this.src = 'http://localhost:2310' + this.$route.query.path + '?zero_preview=true';
api.createPreviewToken('mykey').then(res =>
{
let model = res.data;
this.src = 'http://localhost:2310' + this.$route.query.path + `?${model.queryParameter}=${model.token}`;
});
}
});
</script>
@@ -0,0 +1,13 @@
namespace zero.Backoffice.Endpoints.UI;
public class PreviewRequestTokenModel
{
public string Key { get; set; }
public class Response
{
public string Token { get; set; }
public string QueryParameter { get; set; }
}
}
+19 -1
View File
@@ -6,6 +6,7 @@ using System.IO;
using System.Net.Http;
using System.Reflection;
using zero.Backoffice.Services;
using zero.Preview;
namespace zero.Backoffice.Endpoints.UI;
@@ -19,9 +20,12 @@ public class UIController : ZeroBackofficeController
readonly ICultureService CultureService;
readonly IUserService UserService;
readonly IRequestUrlResolver RequestUrlResolver;
readonly IPreviewService PreviewService;
readonly IZeroContext ZeroContext;
readonly HttpClient HttpClient;
public UIController(IUserService userService, IIconService iconService, IResourceService resourceService, ISectionService sectionService, IZeroOptions options, IMediaManagement media, ICultureService cultureService, IRequestUrlResolver requestUrlResolver)
public UIController(IUserService userService, IIconService iconService, IResourceService resourceService, ISectionService sectionService, IZeroOptions options, IMediaManagement media,
ICultureService cultureService, IRequestUrlResolver requestUrlResolver, IPreviewService previewService, IZeroContext zeroContext)
{
UserService = userService;
IconService = iconService;
@@ -31,6 +35,8 @@ public class UIController : ZeroBackofficeController
Options = options;
Media = media;
RequestUrlResolver = requestUrlResolver;
PreviewService = previewService;
ZeroContext = zeroContext;
HttpClient = new HttpClient();
}
@@ -97,6 +103,18 @@ public class UIController : ZeroBackofficeController
}
[HttpPost("previews/token")]
public async Task<ActionResult<PreviewRequestTokenModel.Response>> GetPreviewToken(PreviewRequestTokenModel model)
{
string token = await PreviewService.CreateAccessToken(model.Key, ZeroContext.BackofficeUser);
return new PreviewRequestTokenModel.Response()
{
Token = token,
QueryParameter = Options.For<PreviewOptions>().QueryParameter
};
}
[HttpGet("thumbnail/{id}-{size}.tmp")]
public async Task<IActionResult> GetThumbnail(string id, string size)
{
+7 -4
View File
@@ -56,12 +56,14 @@ public class ZeroContext : IZeroContext
protected IPrimitiveTypeCollection ValueCollection { get; private set; }
protected IPreviewService PreviewService { get; private set; }
bool _resolved = false;
public ZeroContext(IZeroOptions options, IHttpContextAccessor httpContextAccessor, IApplicationResolver appResolver, ICultureResolver cultureResolver,
ILogger<ZeroContext> logger, IZeroStore store, IHandlerHolder handler, IServiceProvider services)
ILogger<ZeroContext> logger, IZeroStore store, IHandlerHolder handler, IServiceProvider services, IPreviewService previewService)
{
Options = options;
AppResolver = appResolver;
@@ -72,6 +74,7 @@ public class ZeroContext : IZeroContext
ValueCollection = new PrimitiveTypeCollection();
HttpContextAccessor = httpContextAccessor;
Services = services;
PreviewService = previewService;
}
@@ -96,9 +99,6 @@ public class ZeroContext : IZeroContext
_resolved = true;
// check whether the current request is a preview request
IsPreview = context.Request.Query.ContainsKey("zero_preview");
// check if the current request is a backoffice request
IsBackofficeRequest = context.IsBackofficeRequest(Options.ZeroPath);
@@ -112,6 +112,9 @@ public class ZeroContext : IZeroContext
IsLoggedIntoBackoffice = true;
}
// check whether the current request is a preview request
IsPreview = await PreviewService.IsAccessGranted(context.Request, BackofficeUser);
// resolve current application
Application = await AppResolver.Resolve(context, BackofficeUser);
AppId = Application.Id;
@@ -127,6 +127,22 @@ public class ZeroTokenProvider : IZeroTokenProvider
}
/// <inheritdoc />
public virtual async Task<bool> Exists(string token)
{
if (token.IsNullOrWhiteSpace())
{
return false;
}
IZeroDocumentSession session = Store.Session();
// try to find a valid token
SecurityToken securityToken = await session.LoadAsync<SecurityToken>(TokenToId(token));
return securityToken != null;
}
/// <summary>
/// Converts the token to a database id
/// </summary>
@@ -271,4 +287,9 @@ public interface IZeroTokenProvider
/// This method won't store the token in the database and can't be used for verification. Use <see cref="Create(string, TimeSpan, int)"/> instead.
/// </remarks>
string Random(int length);
/// <summary>
/// Determines whether a token exists in the database.
/// </summary>
Task<bool> Exists(string token);
}
+8 -2
View File
@@ -4,8 +4,14 @@ public class PreviewOptions
{
public PreviewOptions()
{
PreviewPath = "/preview";
Path = "/preview";
QueryParameter = "zero_preview";
TokenExpirationInMinutes = 10;
}
public string PreviewPath { get; set; }
public string Path { get; set; }
public string QueryParameter { get; set; }
public double TokenExpirationInMinutes { get; set; }
}
+87
View File
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
namespace zero.Preview;
public class PreviewService : IPreviewService
{
protected IZeroTokenProvider TokenProvider { get; private set; }
protected IZeroOptions Options { get; private set; }
public PreviewService(IZeroTokenProvider tokenProvider, IZeroOptions options)
{
TokenProvider = tokenProvider;
Options = options;
}
/// <inheritdoc />
public async Task<string> CreateAccessToken(string key, ClaimsPrincipal backofficeUser)
{
string fullKey = GetKey(key, backofficeUser);
return await TokenProvider.Create(fullKey, TimeSpan.FromMinutes(Options.For<PreviewOptions>().TokenExpirationInMinutes), 32);
}
/// <inheritdoc />
public async Task<bool> IsAccessGranted(HttpRequest request, ClaimsPrincipal backofficeUser)
{
string token = request.Query[Options.For<PreviewOptions>().QueryParameter];
if (!token.HasValue())
{
return false;
}
return await IsAccessGranted(token, backofficeUser);
}
/// <inheritdoc />
public async Task<bool> IsAccessGranted(string token, ClaimsPrincipal backofficeUser)
{
return await TokenProvider.Exists(token);
}
/// <summary>
/// Generate the key for a new preview access
/// </summary>
protected string GetKey(string keyPrefix, ClaimsPrincipal backofficeUser)
{
//string userId = UserManager.GetUserId(backofficeUser);
//if (userId.IsNullOrEmpty())
//{
// throw new InvalidOperationException("Preview access is only granted for authenticated users");
//}
if (keyPrefix.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(keyPrefix), "Please define a key for generating preview access");
}
return "zero:preview:" + /*userId + ":" + */ keyPrefix;
}
}
public interface IPreviewService
{
/// <summary>
/// Create a token for a new preview access.
/// </summary>
Task<string> CreateAccessToken(string key, ClaimsPrincipal backofficeUser);
// <summary>
/// Determine whether the access to the preview is granted based on an HTTP request
/// </summary>
Task<bool> IsAccessGranted(HttpRequest request, ClaimsPrincipal backofficeUser);
/// <summary>
/// Determine whether the access to the preview is granted based on the passed token
/// </summary>
Task<bool> IsAccessGranted(string token, ClaimsPrincipal backofficeUser);
}
@@ -12,7 +12,7 @@ public class PreviewRouteProvider : ZeroRouteProvider<PreviewRouteModel>
public override async Task<Route> Create(RoutingContext context, PreviewRouteModel model)
{
Route route = await base.Create(context, model);
route.Url = context.Context.Options.For<PreviewOptions>().PreviewPath.EnsureStartsWith('/').TrimEnd('/');
route.Url = context.Context.Options.For<PreviewOptions>().Path.EnsureStartsWith('/').TrimEnd('/');
return route;
}
+2 -4
View File
@@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace zero.Preview;
@@ -9,7 +7,7 @@ internal class ZeroPreviewModule : ZeroModule
{
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
//services.AddScoped<IRouteProvider, PreviewRouteProvider>();
services.AddScoped<IPreviewService, PreviewService>();
services.AddOptions<PreviewOptions>().Bind(configuration.GetSection("Zero:Preview"));
}
}
+1
View File
@@ -21,6 +21,7 @@ global using zero.Media;
global using zero.Models;
global using zero.Pages;
global using zero.Persistence;
global using zero.Preview;
global using zero.Rendering;
global using zero.Routing;
global using zero.Stores;
+1
View File
@@ -52,6 +52,7 @@ public class ZeroBuilder
Modules.Add<ZeroMediaModule>();
Modules.Add<ZeroPageModule>();
Modules.Add<ZeroPersistenceModule>();
Modules.Add<ZeroPreviewModule>();
Modules.Add<ZeroRenderingModule>();
Modules.Add<ZeroRoutingModule>();
Modules.Add<ZeroSpaceModule>();