mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-19 04:58:09 +00:00
Move auth into middleware (#3133)
Closes #2098. This cleans up the authentication a bit; after this change we have two stages in the middleware pipeline: - `AuthenticationMiddleware` reads the JWT token (it does not validate it, this is done by the Azure Functions service) and stores it in `FunctionContext.Items["ONEFUZZ_USER_INFO"]` - `AuthorizationMiddleware` checks the user info against the `[Authorize]` attribute to see if the user has the required permissions - Functions can read the user info from the `FunctionContext` if needed The authorize attribute can be `[Authorize(Allow.User)]` or `Allow.Agent` or `Allow.Admin`. The `Admin` case is new and allows this to be declaratively specified rather than being checked in code. We have several functions which could be changed to use this (e.g. Pool POST/DELETE/PATCH, Scaleset POST/DELETE/PATCH), but I have only changed one so far (JinjaToScriban). One of the benefits here is that this simplifies the test code a lot: we can set the desired user info directly onto our `(Test)FunctionContext` rather than having to supply a fake that pretends to parse the token from the HTTP request. This will also have benefits when running the service locally for testing purposes (refer to internal issue). The other benefit is the ability to programmatically read the required authentication for each function, which may help with Swagger generation.
This commit is contained in:
16
src/ApiService/ApiService/Auth/AuthenticationItems.cs
Normal file
16
src/ApiService/ApiService/Auth/AuthenticationItems.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
|
public static class AuthenticationItems {
|
||||||
|
private const string Key = "ONEFUZZ_USER_INFO";
|
||||||
|
|
||||||
|
public static void SetUserAuthInfo(this FunctionContext context, UserAuthInfo info)
|
||||||
|
=> context.Items[Key] = info;
|
||||||
|
|
||||||
|
public static UserAuthInfo GetUserAuthInfo(this FunctionContext context)
|
||||||
|
=> (UserAuthInfo)context.Items[Key];
|
||||||
|
|
||||||
|
public static UserAuthInfo? TryGetUserAuthInfo(this FunctionContext context)
|
||||||
|
=> context.Items.TryGetValue(Key, out var result) ? (UserAuthInfo)result : null;
|
||||||
|
}
|
111
src/ApiService/ApiService/Auth/AuthenticationMiddleware.cs
Normal file
111
src/ApiService/ApiService/Auth/AuthenticationMiddleware.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.Azure.Functions.Worker.Middleware;
|
||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
|
public sealed class AuthenticationMiddleware : IFunctionsWorkerMiddleware {
|
||||||
|
private readonly IConfigOperations _config;
|
||||||
|
private readonly ILogTracer _log;
|
||||||
|
|
||||||
|
public AuthenticationMiddleware(IConfigOperations config, ILogTracer log) {
|
||||||
|
_config = config;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Async.Task Invoke(FunctionContext context, FunctionExecutionDelegate next) {
|
||||||
|
var requestData = await context.GetHttpRequestDataAsync();
|
||||||
|
if (requestData is not null) {
|
||||||
|
var authToken = GetAuthToken(requestData);
|
||||||
|
if (authToken is not null) {
|
||||||
|
// note that no validation of the token is performed here
|
||||||
|
// this is done globally by Azure Functions; see the configuration in
|
||||||
|
// 'function.bicep'
|
||||||
|
var token = new JwtSecurityToken(authToken);
|
||||||
|
var allowedTenants = await AllowedTenants();
|
||||||
|
if (!allowedTenants.Contains(token.Issuer)) {
|
||||||
|
await BadIssuer(requestData, context, token, allowedTenants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.SetUserAuthInfo(UserInfoFromAuthToken(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UserAuthInfo UserInfoFromAuthToken(JwtSecurityToken token)
|
||||||
|
=> token.Payload.Claims.Aggregate(
|
||||||
|
seed: new UserAuthInfo(new UserInfo(null, null, null), new List<string>()),
|
||||||
|
(acc, claim) => {
|
||||||
|
switch (claim.Type) {
|
||||||
|
case "oid":
|
||||||
|
return acc with { UserInfo = acc.UserInfo with { ObjectId = Guid.Parse(claim.Value) } };
|
||||||
|
case "appid":
|
||||||
|
return acc with { UserInfo = acc.UserInfo with { ApplicationId = Guid.Parse(claim.Value) } };
|
||||||
|
case "upn":
|
||||||
|
return acc with { UserInfo = acc.UserInfo with { Upn = claim.Value } };
|
||||||
|
case "roles":
|
||||||
|
acc.Roles.Add(claim.Value);
|
||||||
|
return acc;
|
||||||
|
default:
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private async Async.ValueTask BadIssuer(
|
||||||
|
HttpRequestData request,
|
||||||
|
FunctionContext context,
|
||||||
|
JwtSecurityToken token,
|
||||||
|
IEnumerable<string> allowedTenants) {
|
||||||
|
|
||||||
|
var tenantsStr = string.Join("; ", allowedTenants);
|
||||||
|
_log.Error($"issuer not from allowed tenant. issuer: {token.Issuer:Tag:Issuer} - tenants: {tenantsStr:Tag:Tenants}");
|
||||||
|
|
||||||
|
var response = HttpResponseData.CreateResponse(request);
|
||||||
|
var status = HttpStatusCode.BadRequest;
|
||||||
|
await response.WriteAsJsonAsync(
|
||||||
|
new ProblemDetails(
|
||||||
|
status,
|
||||||
|
new Error(
|
||||||
|
ErrorCode.INVALID_REQUEST,
|
||||||
|
new List<string> {
|
||||||
|
"unauthorized AAD issuer. If multi-tenant auth is failing, make sure to include all tenant_ids in the `allowed_aad_tenants` list in the instance_config. To see the current instance_config, run `onefuzz instance_config get`. "
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
"application/problem+json",
|
||||||
|
status);
|
||||||
|
|
||||||
|
context.GetInvocationResult().Value = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Async.Task<IEnumerable<string>> AllowedTenants() {
|
||||||
|
var config = await _config.Fetch();
|
||||||
|
return config.AllowedAadTenants.Select(t => $"https://sts.windows.net/{t}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetAuthToken(HttpRequestData requestData)
|
||||||
|
=> GetBearerToken(requestData) ?? GetAadIdToken(requestData);
|
||||||
|
|
||||||
|
private static string? GetAadIdToken(HttpRequestData requestData) {
|
||||||
|
if (!requestData.Headers.TryGetValues("x-ms-token-aad-id-token", out var values)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetBearerToken(HttpRequestData requestData) {
|
||||||
|
if (!requestData.Headers.TryGetValues("Authorization", out var values)
|
||||||
|
|| !AuthenticationHeaderValue.TryParse(values.First(), out var headerValue)
|
||||||
|
|| !string.Equals(headerValue.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headerValue.Parameter;
|
||||||
|
}
|
||||||
|
}
|
103
src/ApiService/ApiService/Auth/AuthorizationMiddleware.cs
Normal file
103
src/ApiService/ApiService/Auth/AuthorizationMiddleware.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.Azure.Functions.Worker.Middleware;
|
||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
|
public sealed class AuthorizationMiddleware : IFunctionsWorkerMiddleware {
|
||||||
|
private readonly IEndpointAuthorization _auth;
|
||||||
|
private readonly ILogTracer _log;
|
||||||
|
|
||||||
|
public AuthorizationMiddleware(IEndpointAuthorization auth, ILogTracer log) {
|
||||||
|
_auth = auth;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Async.Task Invoke(FunctionContext context, FunctionExecutionDelegate next) {
|
||||||
|
var attribute = GetAuthorizeAttribute(context);
|
||||||
|
if (attribute is not null) {
|
||||||
|
var req = await context.GetHttpRequestDataAsync() ?? throw new NotSupportedException("no HTTP request data found");
|
||||||
|
var user = context.TryGetUserAuthInfo();
|
||||||
|
if (user is null) {
|
||||||
|
await Reject(req, context, "no authentication");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (isAgent, _) = await _auth.IsAgent(user);
|
||||||
|
if (isAgent) {
|
||||||
|
if (attribute.Allow != Allow.Agent) {
|
||||||
|
await Reject(req, context, "endpoint not allowed for agents");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attribute.Allow == Allow.Agent) {
|
||||||
|
await Reject(req, context, "endpoint not allowed for users");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(attribute.Allow is Allow.User or Allow.Admin);
|
||||||
|
|
||||||
|
// check access control first
|
||||||
|
var access = await _auth.CheckAccess(req);
|
||||||
|
if (!access.IsOk) {
|
||||||
|
await Reject(req, context, "access control rejected request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check admin next
|
||||||
|
if (attribute.Allow == Allow.Admin) {
|
||||||
|
var adminAccess = await _auth.CheckRequireAdmins(user);
|
||||||
|
if (!adminAccess.IsOk) {
|
||||||
|
await Reject(req, context, "must be admin to use this endpoint");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Async.ValueTask Reject(HttpRequestData request, FunctionContext context, string reason) {
|
||||||
|
var response = HttpResponseData.CreateResponse(request);
|
||||||
|
var status = HttpStatusCode.Unauthorized;
|
||||||
|
await response.WriteAsJsonAsync(
|
||||||
|
new ProblemDetails(
|
||||||
|
status,
|
||||||
|
Error.Create(ErrorCode.UNAUTHORIZED, reason)),
|
||||||
|
"application/problem+json",
|
||||||
|
status);
|
||||||
|
|
||||||
|
context.GetInvocationResult().Value = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use ImmutableDictionary to prevent needing to lock and without the overhead
|
||||||
|
// of ConcurrentDictionary
|
||||||
|
private static ImmutableDictionary<string, AuthorizeAttribute?> _authorizeCache =
|
||||||
|
ImmutableDictionary.Create<string, AuthorizeAttribute?>();
|
||||||
|
|
||||||
|
private static AuthorizeAttribute? GetAuthorizeAttribute(FunctionContext context) {
|
||||||
|
// fully-qualified name of the method
|
||||||
|
var entryPoint = context.FunctionDefinition.EntryPoint;
|
||||||
|
if (_authorizeCache.TryGetValue(entryPoint, out var cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastDot = entryPoint.LastIndexOf('.');
|
||||||
|
var (typeName, methodName) = (entryPoint[..lastDot], entryPoint[(lastDot + 1)..]);
|
||||||
|
var assemblyPath = context.FunctionDefinition.PathToAssembly;
|
||||||
|
var assembly = Assembly.LoadFrom(assemblyPath); // should already be loaded
|
||||||
|
var type = assembly.GetType(typeName)!;
|
||||||
|
var method = type.GetMethod(methodName)!;
|
||||||
|
var result =
|
||||||
|
method.GetCustomAttribute<AuthorizeAttribute>()
|
||||||
|
?? type.GetCustomAttribute<AuthorizeAttribute>();
|
||||||
|
|
||||||
|
_authorizeCache = _authorizeCache.SetItem(entryPoint, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
17
src/ApiService/ApiService/Auth/AuthorizeAttribute.cs
Normal file
17
src/ApiService/ApiService/Auth/AuthorizeAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
|
public sealed class AuthorizeAttribute : Attribute {
|
||||||
|
public AuthorizeAttribute(Allow allow) {
|
||||||
|
Allow = allow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Allow Allow { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Allow {
|
||||||
|
Agent,
|
||||||
|
User,
|
||||||
|
Admin,
|
||||||
|
|
||||||
|
}
|
@ -1,27 +1,23 @@
|
|||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class AgentCanSchedule {
|
public class AgentCanSchedule {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
|
public AgentCanSchedule(ILogTracer log, IOnefuzzContext context) {
|
||||||
public AgentCanSchedule(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("AgentCanSchedule")]
|
[Function("AgentCanSchedule")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.Agent)]
|
||||||
|
public async Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="agents/can_schedule")]
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="agents/can_schedule")]
|
||||||
HttpRequestData req)
|
HttpRequestData req) {
|
||||||
=> _auth.CallIfAgent(req, Post);
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<CanScheduleRequest>(req);
|
var request = await RequestHandling.ParseRequest<CanScheduleRequest>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
_log.Warning($"Cannot schedule due to {request.ErrorV}");
|
_log.Warning($"Cannot schedule due to {request.ErrorV}");
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class AgentCommands {
|
public class AgentCommands {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public AgentCommands(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public AgentCommands(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("AgentCommands")]
|
[Function("AgentCommands")]
|
||||||
|
[Authorize(Allow.Agent)]
|
||||||
public Async.Task<HttpResponseData> Run(
|
public Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "DELETE", Route="agents/commands")]
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "DELETE", Route="agents/commands")]
|
||||||
HttpRequestData req)
|
HttpRequestData req)
|
||||||
=> _auth.CallIfAgent(req, r => r.Method switch {
|
=> req.Method switch {
|
||||||
"GET" => Get(req),
|
"GET" => Get(req),
|
||||||
"DELETE" => Delete(req),
|
"DELETE" => Delete(req),
|
||||||
_ => throw new NotSupportedException($"HTTP Method {req.Method} is not supported for this method")
|
_ => throw new NotSupportedException($"HTTP Method {req.Method} is not supported for this method")
|
||||||
});
|
};
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<NodeCommandGet>(req);
|
var request = await RequestHandling.ParseRequest<NodeCommandGet>(req);
|
||||||
|
@ -1,28 +1,25 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class AgentEvents {
|
public class AgentEvents {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public AgentEvents(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public AgentEvents(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("AgentEvents")]
|
[Function("AgentEvents")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.Agent)]
|
||||||
|
public async Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="agents/events")]
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="agents/events")]
|
||||||
HttpRequestData req)
|
HttpRequestData req) {
|
||||||
=> _auth.CallIfAgent(req, Post);
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<NodeStateEnvelope>(req);
|
var request = await RequestHandling.ParseRequest<NodeStateEnvelope>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, context: "node event");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, context: "node event");
|
||||||
|
@ -1,33 +1,31 @@
|
|||||||
using Azure.Storage.Sas;
|
using Azure.Storage.Sas;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class AgentRegistration {
|
public class AgentRegistration {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public AgentRegistration(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public AgentRegistration(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("AgentRegistration")]
|
[Function("AgentRegistration")]
|
||||||
|
[Authorize(Allow.Agent)]
|
||||||
public Async.Task<HttpResponseData> Run(
|
public Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(
|
[HttpTrigger(
|
||||||
AuthorizationLevel.Anonymous,
|
AuthorizationLevel.Anonymous,
|
||||||
"GET", "POST",
|
"GET", "POST",
|
||||||
Route="agents/registration")] HttpRequestData req)
|
Route="agents/registration")] HttpRequestData req)
|
||||||
=> _auth.CallIfAgent(
|
=> req.Method switch {
|
||||||
req,
|
"GET" => Get(req),
|
||||||
r => r.Method switch {
|
"POST" => Post(req),
|
||||||
"GET" => Get(r),
|
var m => throw new InvalidOperationException($"method {m} not supported"),
|
||||||
"POST" => Post(r),
|
};
|
||||||
var m => throw new InvalidOperationException($"method {m} not supported"),
|
|
||||||
});
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseUri<AgentRegistrationGet>(req);
|
var request = await RequestHandling.ParseUri<AgentRegistrationGet>(req);
|
||||||
|
@ -14,11 +14,10 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Function("Config")]
|
[Function("Config")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
public async Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req) {
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET")]
|
||||||
return Get(req);
|
HttpRequestData req) {
|
||||||
}
|
|
||||||
public async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
|
||||||
_log.Info($"getting endpoint config parameters");
|
_log.Info($"getting endpoint config parameters");
|
||||||
|
|
||||||
var endpointParams = new ConfigResponse(
|
var endpointParams = new ConfigResponse(
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
using Azure.Storage.Sas;
|
using Azure.Storage.Sas;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class ContainersFunction {
|
public class ContainersFunction {
|
||||||
private readonly ILogTracer _logger;
|
private readonly ILogTracer _logger;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public ContainersFunction(ILogTracer logger, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public ContainersFunction(ILogTracer logger, IOnefuzzContext context) {
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Containers")]
|
[Function("Containers")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req)
|
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req)
|
||||||
=> _auth.CallIfUser(req, r => r.Method switch {
|
=> req.Method switch {
|
||||||
"GET" => Get(r),
|
"GET" => Get(req),
|
||||||
"POST" => Post(r),
|
"POST" => Post(req),
|
||||||
"DELETE" => Delete(r),
|
"DELETE" => Delete(req),
|
||||||
_ => throw new NotSupportedException(),
|
_ => throw new NotSupportedException(),
|
||||||
});
|
};
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
|
|
||||||
|
@ -2,23 +2,20 @@
|
|||||||
using Azure.Storage.Sas;
|
using Azure.Storage.Sas;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Download {
|
public class Download {
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Download(IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Download(IOnefuzzContext context) {
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Download")]
|
[Function("Download")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req)
|
[Authorize(Allow.User)]
|
||||||
=> _auth.CallIfUser(req, Get);
|
public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req) {
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
|
||||||
var query = HttpUtility.ParseQueryString(req.Url.Query);
|
var query = HttpUtility.ParseQueryString(req.Url.Query);
|
||||||
|
|
||||||
var queryContainer = query["container"];
|
var queryContainer = query["container"];
|
||||||
|
@ -1,27 +1,21 @@
|
|||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class EventsFunction {
|
public class EventsFunction {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public EventsFunction(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public EventsFunction(ILogTracer log, IOnefuzzContext context) {
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Events")]
|
[Function("Events")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req)
|
[Authorize(Allow.User)]
|
||||||
=> _auth.CallIfUser(req, r => r.Method switch {
|
public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req) {
|
||||||
"GET" => Get(r),
|
|
||||||
_ => throw new NotSupportedException(),
|
|
||||||
});
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<EventsGet>(req);
|
var request = await RequestHandling.ParseRequest<EventsGet>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "events get");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "events get");
|
||||||
|
@ -4,17 +4,16 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Info {
|
public class Info {
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly Lazy<Async.Task<InfoResponse>> _response;
|
private readonly Lazy<Async.Task<InfoResponse>> _response;
|
||||||
|
|
||||||
public Info(IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Info(IOnefuzzContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_auth = auth;
|
|
||||||
|
|
||||||
// TODO: this isn’t actually shared between calls at the moment,
|
// TODO: this isn’t actually shared between calls at the moment,
|
||||||
// this needs to be placed into a class that can be registered into the
|
// this needs to be placed into a class that can be registered into the
|
||||||
@ -60,10 +59,8 @@ public class Info {
|
|||||||
return sr.ReadToEnd().Trim();
|
return sr.ReadToEnd().Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> GetResponse(HttpRequestData req)
|
|
||||||
=> await RequestHandling.Ok(req, await _response.Value);
|
|
||||||
|
|
||||||
[Function("Info")]
|
[Function("Info")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req)
|
[Authorize(Allow.User)]
|
||||||
=> _auth.CallIfUser(req, GetResponse);
|
public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req)
|
||||||
|
=> await RequestHandling.Ok(req, await _response.Value);
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,26 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class InstanceConfig {
|
public class InstanceConfig {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public InstanceConfig(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public InstanceConfig(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string Route = "instance_config";
|
||||||
|
|
||||||
[Function("InstanceConfig")]
|
[Function("InstanceConfig")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.User)]
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", Route = "instance_config")] HttpRequestData req) {
|
public async Task<HttpResponseData> Get(
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route=Route)]
|
||||||
"GET" => Get(r),
|
HttpRequestData req) {
|
||||||
"POST" => Post(r),
|
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
|
||||||
_log.Info($"getting instance_config");
|
_log.Info($"getting instance_config");
|
||||||
var config = await _context.ConfigOperations.Fetch();
|
var config = await _context.ConfigOperations.Fetch();
|
||||||
|
|
||||||
@ -34,7 +30,11 @@ public class InstanceConfig {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
[Function("InstanceConfig_Admin")]
|
||||||
|
[Authorize(Allow.Admin)]
|
||||||
|
public async Task<HttpResponseData> Post(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route=Route)]
|
||||||
|
HttpRequestData req) {
|
||||||
_log.Info($"attempting instance_config update");
|
_log.Info($"attempting instance_config update");
|
||||||
var request = await RequestHandling.ParseRequest<InstanceConfigUpdate>(req);
|
var request = await RequestHandling.ParseRequest<InstanceConfigUpdate>(req);
|
||||||
|
|
||||||
@ -44,12 +44,8 @@ public class InstanceConfig {
|
|||||||
request.ErrorV,
|
request.ErrorV,
|
||||||
context: "instance_config update");
|
context: "instance_config update");
|
||||||
}
|
}
|
||||||
var (config, answer) = await (
|
|
||||||
_context.ConfigOperations.Fetch(),
|
var config = await _context.ConfigOperations.Fetch();
|
||||||
_auth.CheckRequireAdmins(req));
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "instance_config update");
|
|
||||||
}
|
|
||||||
var updateNsg = false;
|
var updateNsg = false;
|
||||||
if (request.OkV.config.ProxyNsgConfig is NetworkSecurityGroupConfig requestConfig
|
if (request.OkV.config.ProxyNsgConfig is NetworkSecurityGroupConfig requestConfig
|
||||||
&& config.ProxyNsgConfig is NetworkSecurityGroupConfig currentConfig) {
|
&& config.ProxyNsgConfig is NetworkSecurityGroupConfig currentConfig) {
|
||||||
@ -58,7 +54,9 @@ public class InstanceConfig {
|
|||||||
updateNsg = true;
|
updateNsg = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _context.ConfigOperations.Save(request.OkV.config, false, false);
|
await _context.ConfigOperations.Save(request.OkV.config, false, false);
|
||||||
|
|
||||||
if (updateNsg) {
|
if (updateNsg) {
|
||||||
await foreach (var nsg in _context.NsgOperations.ListNsgs()) {
|
await foreach (var nsg in _context.NsgOperations.ListNsgs()) {
|
||||||
_log.Info($"Checking if nsg: {nsg.Data.Location!:Tag:Location} ({nsg.Data.Name:Tag:NsgName}) owned by OneFuzz");
|
_log.Info($"Checking if nsg: {nsg.Data.Location!:Tag:Location} ({nsg.Data.Name:Tag:NsgName}) owned by OneFuzz");
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Jobs {
|
public class Jobs {
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly ILogTracer _logTracer;
|
private readonly ILogTracer _logTracer;
|
||||||
|
|
||||||
public Jobs(IEndpointAuthorization auth, IOnefuzzContext context, ILogTracer logTracer) {
|
public Jobs(IOnefuzzContext context, ILogTracer logTracer) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_auth = auth;
|
|
||||||
_logTracer = logTracer;
|
_logTracer = logTracer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Jobs")]
|
[Function("Jobs")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req)
|
[Authorize(Allow.User)]
|
||||||
=> _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")]
|
||||||
"DELETE" => Delete(r),
|
HttpRequestData req,
|
||||||
"POST" => Post(r),
|
FunctionContext context)
|
||||||
|
=> req.Method switch {
|
||||||
|
"GET" => Get(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
|
"POST" => Post(req, context),
|
||||||
var m => throw new NotSupportedException($"Unsupported HTTP method {m}"),
|
var m => throw new NotSupportedException($"Unsupported HTTP method {m}"),
|
||||||
});
|
};
|
||||||
|
|
||||||
private async Task<HttpResponseData> Post(HttpRequestData req) {
|
private async Task<HttpResponseData> Post(HttpRequestData req, FunctionContext context) {
|
||||||
var request = await RequestHandling.ParseRequest<JobCreate>(req);
|
var request = await RequestHandling.ParseRequest<JobCreate>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "jobs create");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "jobs create");
|
||||||
}
|
}
|
||||||
|
|
||||||
var userInfo = await _context.UserCredentials.ParseJwtToken(req);
|
var userInfo = context.GetUserAuthInfo();
|
||||||
if (!userInfo.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, userInfo.ErrorV, "jobs create");
|
|
||||||
}
|
|
||||||
|
|
||||||
var create = request.OkV;
|
var create = request.OkV;
|
||||||
var cfg = new JobConfig(
|
var cfg = new JobConfig(
|
||||||
@ -47,7 +47,7 @@ public class Jobs {
|
|||||||
JobId: Guid.NewGuid(),
|
JobId: Guid.NewGuid(),
|
||||||
State: JobState.Init,
|
State: JobState.Init,
|
||||||
Config: cfg) {
|
Config: cfg) {
|
||||||
UserInfo = userInfo.OkV.UserInfo,
|
UserInfo = userInfo.UserInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
// create the job logs container
|
// create the job logs container
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class JinjaToScriban {
|
public class JinjaToScriban {
|
||||||
|
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
|
|
||||||
public JinjaToScriban(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public JinjaToScriban(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("JinjaToScriban")]
|
[Function("JinjaToScriban")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.Admin)]
|
||||||
|
public async Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="migrations/jinja_to_scriban")]
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="migrations/jinja_to_scriban")]
|
||||||
HttpRequestData req)
|
HttpRequestData req) {
|
||||||
=> _auth.CallIfUser(req, Post);
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<JinjaToScribanMigrationPost>(req);
|
var request = await RequestHandling.ParseRequest<JinjaToScribanMigrationPost>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
@ -31,11 +29,6 @@ public class JinjaToScriban {
|
|||||||
"JinjaToScriban");
|
"JinjaToScriban");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "JinjaToScriban");
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.Info($"Finding notifications to migrate");
|
_log.Info($"Finding notifications to migrate");
|
||||||
|
|
||||||
var notifications = _context.NotificationOperations.SearchAll()
|
var notifications = _context.NotificationOperations.SearchAll()
|
||||||
|
@ -2,32 +2,24 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Negotiate {
|
public class Negotiate {
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
public Negotiate(IEndpointAuthorization auth) {
|
|
||||||
_auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Function("Negotiate")]
|
[Function("Negotiate")]
|
||||||
public Task<HttpResponseData> Run(
|
[Authorize(Allow.User)]
|
||||||
|
public static async Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST")] HttpRequestData req,
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST")] HttpRequestData req,
|
||||||
[SignalRConnectionInfoInput(HubName = "dashboard")] string info)
|
[SignalRConnectionInfoInput(HubName = "dashboard")] string info) {
|
||||||
=> _auth.CallIfUser(req, r => r.Method switch {
|
|
||||||
"POST" => Post(r, info),
|
|
||||||
var m => throw new InvalidOperationException($"Unsupported HTTP method {m}"),
|
|
||||||
});
|
|
||||||
|
|
||||||
// This endpoint handles the signalr negotation
|
// This endpoint handles the signalr negotation
|
||||||
// As we do not differentiate from clients at this time, we pass the Functions runtime
|
// As we do not differentiate from clients at this time, we pass the Functions runtime
|
||||||
// provided connection straight to the client
|
// provided connection straight to the client
|
||||||
//
|
//
|
||||||
// For more info:
|
// For more info:
|
||||||
// https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-internals
|
// https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-internals
|
||||||
|
|
||||||
private static async Task<HttpResponseData> Post(HttpRequestData req, string info) {
|
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
resp.Headers.Add("Content-Type", "application/json");
|
resp.Headers.Add("Content-Type", "application/json");
|
||||||
await resp.WriteStringAsync(info);
|
await resp.WriteStringAsync(info);
|
||||||
|
@ -1,30 +1,42 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Node {
|
public class Node {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Node(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Node(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string Route = "node";
|
||||||
|
|
||||||
[Function("Node")]
|
[Function("Node")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "PATCH", "POST", "DELETE")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route=Route)]
|
||||||
"PATCH" => Patch(r),
|
HttpRequestData req)
|
||||||
"POST" => Post(r),
|
=> req.Method switch {
|
||||||
"DELETE" => Delete(r),
|
"GET" => Get(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
[Function("Node_Admin")]
|
||||||
|
[Authorize(Allow.Admin)]
|
||||||
|
public Task<HttpResponseData> Admin(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "PATCH", "POST", "DELETE", Route=Route)]
|
||||||
|
HttpRequestData req)
|
||||||
|
=> req.Method switch {
|
||||||
|
"PATCH" => Patch(req),
|
||||||
|
"POST" => Post(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
|
};
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<NodeSearch>(req);
|
var request = await RequestHandling.ParseRequest<NodeSearch>(req);
|
||||||
@ -82,11 +94,6 @@ public class Node {
|
|||||||
"NodeReimage");
|
"NodeReimage");
|
||||||
}
|
}
|
||||||
|
|
||||||
var authCheck = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!authCheck.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeReimage");
|
|
||||||
}
|
|
||||||
|
|
||||||
var patch = request.OkV;
|
var patch = request.OkV;
|
||||||
var node = await _context.NodeOperations.GetByMachineId(patch.MachineId);
|
var node = await _context.NodeOperations.GetByMachineId(patch.MachineId);
|
||||||
if (node is null) {
|
if (node is null) {
|
||||||
@ -116,11 +123,6 @@ public class Node {
|
|||||||
"NodeUpdate");
|
"NodeUpdate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var authCheck = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!authCheck.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeUpdate");
|
|
||||||
}
|
|
||||||
|
|
||||||
var post = request.OkV;
|
var post = request.OkV;
|
||||||
var node = await _context.NodeOperations.GetByMachineId(post.MachineId);
|
var node = await _context.NodeOperations.GetByMachineId(post.MachineId);
|
||||||
if (node is null) {
|
if (node is null) {
|
||||||
@ -150,11 +152,6 @@ public class Node {
|
|||||||
context: "NodeDelete");
|
context: "NodeDelete");
|
||||||
}
|
}
|
||||||
|
|
||||||
var authCheck = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!authCheck.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeDelete");
|
|
||||||
}
|
|
||||||
|
|
||||||
var delete = request.OkV;
|
var delete = request.OkV;
|
||||||
var node = await _context.NodeOperations.GetByMachineId(delete.MachineId);
|
var node = await _context.NodeOperations.GetByMachineId(delete.MachineId);
|
||||||
if (node is null) {
|
if (node is null) {
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class NodeAddSshKey {
|
public class NodeAddSshKey {
|
||||||
|
|
||||||
private readonly ILogTracer _log;
|
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public NodeAddSshKey(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public NodeAddSshKey(IOnefuzzContext context) {
|
||||||
_log = log;
|
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
[Function("NodeAddSshKey")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
|
public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "node/add_ssh_key")] HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<NodeAddSshKeyPost>(req);
|
var request = await RequestHandling.ParseRequest<NodeAddSshKeyPost>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
@ -42,16 +40,5 @@ public class NodeAddSshKey {
|
|||||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||||
await response.WriteAsJsonAsync(new BoolResult(true));
|
await response.WriteAsJsonAsync(new BoolResult(true));
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("NodeAddSshKey")]
|
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "node/add_ssh_key")] HttpRequestData req) {
|
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
|
||||||
"POST" => Post(r),
|
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Notifications {
|
public class Notifications {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Notifications(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Notifications(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +81,12 @@ public class Notifications {
|
|||||||
|
|
||||||
|
|
||||||
[Function("Notifications")]
|
[Function("Notifications")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req)
|
||||||
"GET" => Get(r),
|
=> req.Method switch {
|
||||||
"POST" => Post(r),
|
"GET" => Get(req),
|
||||||
"DELETE" => Delete(r),
|
"POST" => Post(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class NotificationsTest {
|
public class NotificationsTest {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public NotificationsTest(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public NotificationsTest(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
[Function("NotificationsTest")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
|
public async Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "notifications/test")] HttpRequestData req) {
|
||||||
_log.WithTag("HttpRequest", "GET").Info($"Notification test");
|
_log.WithTag("HttpRequest", "GET").Info($"Notification test");
|
||||||
var request = await RequestHandling.ParseRequest<NotificationTest>(req);
|
var request = await RequestHandling.ParseRequest<NotificationTest>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
@ -29,13 +30,4 @@ public class NotificationsTest {
|
|||||||
await response.WriteAsJsonAsync(new NotificationTestResponse(result.IsOk, result.ErrorV?.ToString()));
|
await response.WriteAsJsonAsync(new NotificationTestResponse(result.IsOk, result.ErrorV?.ToString()));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Function("NotificationsTest")]
|
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "notifications/test")] HttpRequestData req) {
|
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
|
||||||
"POST" => Post(r),
|
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,40 @@
|
|||||||
using Azure.Storage.Sas;
|
using Azure.Storage.Sas;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Pool {
|
public class Pool {
|
||||||
private readonly ILogTracer _log;
|
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Pool(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Pool(IOnefuzzContext context) {
|
||||||
_log = log;
|
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string Route = "pool";
|
||||||
|
|
||||||
[Function("Pool")]
|
[Function("Pool")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE", "PATCH")] HttpRequestData req)
|
[Authorize(Allow.User)]
|
||||||
=> _auth.CallIfUser(req, r => r.Method switch {
|
public Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route=Route)]
|
||||||
"POST" => Post(r),
|
HttpRequestData req)
|
||||||
"DELETE" => Delete(r),
|
=> req.Method switch {
|
||||||
"PATCH" => Patch(r),
|
"GET" => Get(req),
|
||||||
var m => throw new InvalidOperationException("Unsupported HTTP method {m}"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method {m}"),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
[Function("Pool_Admin")]
|
||||||
|
[Authorize(Allow.Admin)]
|
||||||
|
public Task<HttpResponseData> Admin(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", "DELETE", "PATCH", Route=Route)]
|
||||||
|
HttpRequestData req)
|
||||||
|
=> req.Method switch {
|
||||||
|
"POST" => Post(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
|
"PATCH" => Patch(req),
|
||||||
|
_ => throw new InvalidOperationException("Unsupported HTTP method {m}"),
|
||||||
|
};
|
||||||
|
|
||||||
private async Task<HttpResponseData> Delete(HttpRequestData r) {
|
private async Task<HttpResponseData> Delete(HttpRequestData r) {
|
||||||
var request = await RequestHandling.ParseRequest<PoolStop>(r);
|
var request = await RequestHandling.ParseRequest<PoolStop>(r);
|
||||||
@ -32,11 +43,6 @@ public class Pool {
|
|||||||
return await _context.RequestHandling.NotOk(r, request.ErrorV, "PoolDelete");
|
return await _context.RequestHandling.NotOk(r, request.ErrorV, "PoolDelete");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(r);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(r, answer.ErrorV, "PoolDelete");
|
|
||||||
}
|
|
||||||
|
|
||||||
var poolResult = await _context.PoolOperations.GetByName(request.OkV.Name);
|
var poolResult = await _context.PoolOperations.GetByName(request.OkV.Name);
|
||||||
if (!poolResult.IsOk) {
|
if (!poolResult.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(r, poolResult.ErrorV, "pool stop");
|
return await _context.RequestHandling.NotOk(r, poolResult.ErrorV, "pool stop");
|
||||||
@ -53,11 +59,6 @@ public class Pool {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "PoolCreate");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "PoolCreate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "PoolCreate");
|
|
||||||
}
|
|
||||||
|
|
||||||
var create = request.OkV;
|
var create = request.OkV;
|
||||||
var pool = await _context.PoolOperations.GetByName(create.Name);
|
var pool = await _context.PoolOperations.GetByName(create.Name);
|
||||||
if (pool.IsOk) {
|
if (pool.IsOk) {
|
||||||
@ -77,11 +78,6 @@ public class Pool {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "PoolUpdate");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "PoolUpdate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "PoolUpdate");
|
|
||||||
}
|
|
||||||
|
|
||||||
var update = request.OkV;
|
var update = request.OkV;
|
||||||
var pool = await _context.PoolOperations.GetByName(update.Name);
|
var pool = await _context.PoolOperations.GetByName(update.Name);
|
||||||
if (!pool.IsOk) {
|
if (!pool.IsOk) {
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
using VmProxy = Microsoft.OneFuzz.Service.Proxy;
|
using VmProxy = Microsoft.OneFuzz.Service.Proxy;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Proxy {
|
public class Proxy {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Proxy(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Proxy(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Proxy")]
|
[Function("Proxy")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "PATCH", "POST", "DELETE")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "PATCH", "POST", "DELETE")]
|
||||||
"PATCH" => Patch(r),
|
HttpRequestData req)
|
||||||
"POST" => Post(r),
|
=> req.Method switch {
|
||||||
"DELETE" => Delete(r),
|
"GET" => Get(req),
|
||||||
|
"PATCH" => Patch(req),
|
||||||
|
"POST" => Post(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ProxyGetResult GetResult(ProxyForward proxyForward, VmProxy? proxy) {
|
private ProxyGetResult GetResult(ProxyForward proxyForward, VmProxy? proxy) {
|
||||||
var forward = _context.ProxyForwardOperations.ToForward(proxyForward);
|
var forward = _context.ProxyForwardOperations.ToForward(proxyForward);
|
||||||
|
@ -3,29 +3,31 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class ReproVmss {
|
public class ReproVmss {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public ReproVmss(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public ReproVmss(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("ReproVms")]
|
[Function("ReproVms")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE", Route = "repro_vms")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE", Route = "repro_vms")]
|
||||||
"POST" => Post(r),
|
HttpRequestData req,
|
||||||
"DELETE" => Delete(r),
|
FunctionContext context)
|
||||||
|
=> req.Method switch {
|
||||||
|
"GET" => Get(req),
|
||||||
|
"POST" => Post(req, context),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<ReproGet>(req);
|
var request = await RequestHandling.ParseRequest<ReproGet>(req);
|
||||||
@ -56,7 +58,7 @@ public class ReproVmss {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Post(HttpRequestData req, FunctionContext context) {
|
||||||
var request = await RequestHandling.ParseRequest<ReproCreate>(req);
|
var request = await RequestHandling.ParseRequest<ReproCreate>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
@ -65,13 +67,7 @@ public class ReproVmss {
|
|||||||
"repro_vm create");
|
"repro_vm create");
|
||||||
}
|
}
|
||||||
|
|
||||||
var userInfo = await _context.UserCredentials.ParseJwtToken(req);
|
var userInfo = context.GetUserAuthInfo();
|
||||||
if (!userInfo.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(
|
|
||||||
req,
|
|
||||||
userInfo.ErrorV,
|
|
||||||
"repro_vm create");
|
|
||||||
}
|
|
||||||
|
|
||||||
var create = request.OkV;
|
var create = request.OkV;
|
||||||
var cfg = new ReproConfig(
|
var cfg = new ReproConfig(
|
||||||
@ -79,7 +75,7 @@ public class ReproVmss {
|
|||||||
Path: create.Path,
|
Path: create.Path,
|
||||||
Duration: create.Duration);
|
Duration: create.Duration);
|
||||||
|
|
||||||
var vm = await _context.ReproOperations.Create(cfg, userInfo.OkV.UserInfo);
|
var vm = await _context.ReproOperations.Create(cfg, userInfo.UserInfo);
|
||||||
if (!vm.IsOk) {
|
if (!vm.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
req,
|
req,
|
||||||
@ -98,7 +94,7 @@ public class ReproVmss {
|
|||||||
// we’d like to track the usage of this feature;
|
// we’d like to track the usage of this feature;
|
||||||
// anonymize the user ID so we can distinguish multiple requests
|
// anonymize the user ID so we can distinguish multiple requests
|
||||||
{
|
{
|
||||||
var data = userInfo.OkV.UserInfo.ToString(); // rely on record ToString
|
var data = userInfo.UserInfo.ToString(); // rely on record ToString
|
||||||
var hash = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(data)));
|
var hash = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(data)));
|
||||||
_log.Event($"created repro VM, user distinguisher: {hash:Tag:UserHash}");
|
_log.Event($"created repro VM, user distinguisher: {hash:Tag:UserHash}");
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,42 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Scaleset {
|
public class Scaleset {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Scaleset(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Scaleset(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string Route = "scaleset";
|
||||||
|
|
||||||
[Function("Scaleset")]
|
[Function("Scaleset")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "PATCH", "POST", "DELETE")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route=Route)]
|
||||||
"PATCH" => Patch(r),
|
HttpRequestData req)
|
||||||
"POST" => Post(r),
|
=> req.Method switch {
|
||||||
"DELETE" => Delete(r),
|
"GET" => Get(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
[Function("Scaleset_Admin")]
|
||||||
|
[Authorize(Allow.Admin)]
|
||||||
|
public Async.Task<HttpResponseData> Admin(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "PATCH", "POST", "DELETE", Route=Route)]
|
||||||
|
HttpRequestData req)
|
||||||
|
=> req.Method switch {
|
||||||
|
"PATCH" => Patch(req),
|
||||||
|
"POST" => Post(req),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
|
};
|
||||||
|
|
||||||
private async Task<HttpResponseData> Delete(HttpRequestData req) {
|
private async Task<HttpResponseData> Delete(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<ScalesetStop>(req);
|
var request = await RequestHandling.ParseRequest<ScalesetStop>(req);
|
||||||
@ -32,11 +44,6 @@ public class Scaleset {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetDelete");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetDelete");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "ScalesetDelete");
|
|
||||||
}
|
|
||||||
|
|
||||||
var scalesetResult = await _context.ScalesetOperations.GetById(request.OkV.ScalesetId);
|
var scalesetResult = await _context.ScalesetOperations.GetById(request.OkV.ScalesetId);
|
||||||
if (!scalesetResult.IsOk) {
|
if (!scalesetResult.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetDelete");
|
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetDelete");
|
||||||
@ -54,11 +61,6 @@ public class Scaleset {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetCreate");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetCreate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "ScalesetCreate");
|
|
||||||
}
|
|
||||||
|
|
||||||
var create = request.OkV;
|
var create = request.OkV;
|
||||||
// verify the pool exists
|
// verify the pool exists
|
||||||
var poolResult = await _context.PoolOperations.GetByName(create.PoolName);
|
var poolResult = await _context.PoolOperations.GetByName(create.PoolName);
|
||||||
@ -121,7 +123,7 @@ public class Scaleset {
|
|||||||
ScalesetId: Service.Scaleset.GenerateNewScalesetId(create.PoolName),
|
ScalesetId: Service.Scaleset.GenerateNewScalesetId(create.PoolName),
|
||||||
State: ScalesetState.Init,
|
State: ScalesetState.Init,
|
||||||
NeedsConfigUpdate: false,
|
NeedsConfigUpdate: false,
|
||||||
Auth: new SecretValue<Authentication>(await Auth.BuildAuth(_log)),
|
Auth: new SecretValue<Authentication>(await AuthHelpers.BuildAuth(_log)),
|
||||||
PoolName: create.PoolName,
|
PoolName: create.PoolName,
|
||||||
VmSku: create.VmSku,
|
VmSku: create.VmSku,
|
||||||
Image: image,
|
Image: image,
|
||||||
@ -172,11 +174,6 @@ public class Scaleset {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetUpdate");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetUpdate");
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await _auth.CheckRequireAdmins(req);
|
|
||||||
if (!answer.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, answer.ErrorV, "ScalesetUpdate");
|
|
||||||
}
|
|
||||||
|
|
||||||
var scalesetResult = await _context.ScalesetOperations.GetById(request.OkV.ScalesetId);
|
var scalesetResult = await _context.ScalesetOperations.GetById(request.OkV.ScalesetId);
|
||||||
if (!scalesetResult.IsOk) {
|
if (!scalesetResult.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetUpdate");
|
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetUpdate");
|
||||||
|
@ -2,29 +2,29 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Tasks {
|
public class Tasks {
|
||||||
private readonly ILogTracer _log;
|
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Tasks(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Tasks(IOnefuzzContext context) {
|
||||||
_log = log;
|
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Tasks")]
|
[Function("Tasks")]
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")] HttpRequestData req) {
|
[Authorize(Allow.User)]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
public Async.Task<HttpResponseData> Run(
|
||||||
"GET" => Get(r),
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE")]
|
||||||
"POST" => Post(r),
|
HttpRequestData req,
|
||||||
"DELETE" => Delete(r),
|
FunctionContext context)
|
||||||
|
=> req.Method switch {
|
||||||
|
"GET" => Get(req),
|
||||||
|
"POST" => Post(req, context),
|
||||||
|
"DELETE" => Delete(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<TaskSearch>(req);
|
var request = await RequestHandling.ParseRequest<TaskSearch>(req);
|
||||||
@ -73,7 +73,7 @@ public class Tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Post(HttpRequestData req, FunctionContext context) {
|
||||||
var request = await RequestHandling.ParseRequest<TaskCreate>(req);
|
var request = await RequestHandling.ParseRequest<TaskCreate>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
@ -82,10 +82,7 @@ public class Tasks {
|
|||||||
"task create");
|
"task create");
|
||||||
}
|
}
|
||||||
|
|
||||||
var userInfo = await _context.UserCredentials.ParseJwtToken(req);
|
var userInfo = context.GetUserAuthInfo();
|
||||||
if (!userInfo.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, userInfo.ErrorV, "task create");
|
|
||||||
}
|
|
||||||
|
|
||||||
var create = request.OkV;
|
var create = request.OkV;
|
||||||
var cfg = new TaskConfig(
|
var cfg = new TaskConfig(
|
||||||
@ -141,7 +138,7 @@ public class Tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = await _context.TaskOperations.Create(cfg, cfg.JobId, userInfo.OkV.UserInfo);
|
var task = await _context.TaskOperations.Create(cfg, cfg.JobId, userInfo.UserInfo);
|
||||||
|
|
||||||
if (!task.IsOk) {
|
if (!task.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
public class Tools {
|
public class Tools {
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
|
|
||||||
public Tools(IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Tools(IOnefuzzContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_auth = auth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<HttpResponseData> GetResponse(HttpRequestData req) {
|
[Function("Tools")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
|
public async Async.Task<HttpResponseData> Run(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req) {
|
||||||
|
|
||||||
//Note: streaming response are not currently supported by in isolated functions
|
//Note: streaming response are not currently supported by in isolated functions
|
||||||
// https://github.com/Azure/azure-functions-dotnet-worker/issues/958
|
// https://github.com/Azure/azure-functions-dotnet-worker/issues/958
|
||||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||||
@ -23,9 +26,4 @@ public class Tools {
|
|||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Function("Tools")]
|
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "GET")] HttpRequestData req)
|
|
||||||
=> _auth.CallIfUser(req, GetResponse);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
|
|
||||||
@ -11,7 +12,11 @@ public class ValidateScriban {
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
[Function("ValidateScriban")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
|
public async Async.Task<HttpResponseData> Run(
|
||||||
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST")]
|
||||||
|
HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<TemplateValidationPost>(req);
|
var request = await RequestHandling.ParseRequest<TemplateValidationPost>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ValidateTemplate");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ValidateTemplate");
|
||||||
@ -27,14 +32,5 @@ public class ValidateScriban {
|
|||||||
$"Template failed to render due to: `{e.Message}`"
|
$"Template failed to render due to: `{e.Message}`"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Function("ValidateScriban")]
|
|
||||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST")] HttpRequestData req) {
|
|
||||||
return req.Method switch {
|
|
||||||
"POST" => Post(req),
|
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,28 +3,22 @@
|
|||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
public class WebhookLogs {
|
public class WebhookLogs {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public WebhookLogs(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public WebhookLogs(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("WebhookLogs")]
|
[Function("WebhookLogs")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.User)]
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "webhooks/logs")] HttpRequestData req) {
|
public async Async.Task<HttpResponseData> Run(
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "webhooks/logs")]
|
||||||
"POST" => Post(r),
|
HttpRequestData req) {
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<WebhookGet>(req);
|
var request = await RequestHandling.ParseRequest<WebhookGet>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
|
@ -3,28 +3,22 @@
|
|||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
public class WebhookPing {
|
public class WebhookPing {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public WebhookPing(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public WebhookPing(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("WebhookPing")]
|
[Function("WebhookPing")]
|
||||||
public Async.Task<HttpResponseData> Run(
|
[Authorize(Allow.User)]
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "webhooks/ping")] HttpRequestData req) {
|
public async Async.Task<HttpResponseData> Run(
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "webhooks/ping")]
|
||||||
"POST" => Post(r),
|
HttpRequestData req) {
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
|
||||||
var request = await RequestHandling.ParseRequest<WebhookGet>(req);
|
var request = await RequestHandling.ParseRequest<WebhookGet>(req);
|
||||||
if (!request.IsOk) {
|
if (!request.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
|
@ -3,31 +3,29 @@
|
|||||||
namespace Microsoft.OneFuzz.Service.Functions;
|
namespace Microsoft.OneFuzz.Service.Functions;
|
||||||
using Microsoft.Azure.Functions.Worker;
|
using Microsoft.Azure.Functions.Worker;
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
public class Webhooks {
|
public class Webhooks {
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly IEndpointAuthorization _auth;
|
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
public Webhooks(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
public Webhooks(ILogTracer log, IOnefuzzContext context) {
|
||||||
_log = log;
|
_log = log;
|
||||||
_auth = auth;
|
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Function("Webhooks")]
|
[Function("Webhooks")]
|
||||||
|
[Authorize(Allow.User)]
|
||||||
public Async.Task<HttpResponseData> Run(
|
public Async.Task<HttpResponseData> Run(
|
||||||
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE", "PATCH")] HttpRequestData req) {
|
[HttpTrigger(AuthorizationLevel.Anonymous, "GET", "POST", "DELETE", "PATCH")]
|
||||||
return _auth.CallIfUser(req, r => r.Method switch {
|
HttpRequestData req)
|
||||||
"GET" => Get(r),
|
=> req.Method switch {
|
||||||
"POST" => Post(r),
|
"GET" => Get(req),
|
||||||
"DELETE" => Delete(r),
|
"POST" => Post(req),
|
||||||
"PATCH" => Patch(r),
|
"DELETE" => Delete(req),
|
||||||
|
"PATCH" => Patch(req),
|
||||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var request = await RequestHandling.ParseRequest<WebhookSearch>(req);
|
var request = await RequestHandling.ParseRequest<WebhookSearch>(req);
|
||||||
|
@ -9,7 +9,7 @@ using TokenType = String;
|
|||||||
public class Request {
|
public class Request {
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
Func<Task<(TokenType, AccessToken)>>? _auth;
|
private readonly Func<Task<(TokenType, AccessToken)>>? _auth;
|
||||||
|
|
||||||
public Request(HttpClient httpClient, Func<Task<(TokenType, AccessToken)>>? auth = null) {
|
public Request(HttpClient httpClient, Func<Task<(TokenType, AccessToken)>>? auth = null) {
|
||||||
_auth = auth;
|
_auth = auth;
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
// to avoid collision with Task in model.cs
|
global using System;
|
||||||
global using System;
|
global using System.Collections.Generic;
|
||||||
global
|
global using System.Linq;
|
||||||
using System.Collections.Generic;
|
// to avoid collision with Task in model.cs
|
||||||
global
|
global using Async = System.Threading.Tasks;
|
||||||
using System.Linq;
|
|
||||||
global
|
|
||||||
using Async = System.Threading.Tasks;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ApiService.OneFuzzLib.Orm;
|
using ApiService.OneFuzzLib.Orm;
|
||||||
using Azure.Core.Serialization;
|
using Azure.Core.Serialization;
|
||||||
@ -99,7 +96,6 @@ public class Program {
|
|||||||
.AddScoped<IContainers, Containers>()
|
.AddScoped<IContainers, Containers>()
|
||||||
.AddScoped<IReports, Reports>()
|
.AddScoped<IReports, Reports>()
|
||||||
.AddScoped<INotificationOperations, NotificationOperations>()
|
.AddScoped<INotificationOperations, NotificationOperations>()
|
||||||
.AddScoped<IUserCredentials, UserCredentials>()
|
|
||||||
.AddScoped<IReproOperations, ReproOperations>()
|
.AddScoped<IReproOperations, ReproOperations>()
|
||||||
.AddScoped<IPoolOperations, PoolOperations>()
|
.AddScoped<IPoolOperations, PoolOperations>()
|
||||||
.AddScoped<IIpOperations, IpOperations>()
|
.AddScoped<IIpOperations, IpOperations>()
|
||||||
@ -140,6 +136,8 @@ public class Program {
|
|||||||
.ConfigureFunctionsWorkerDefaults(builder => {
|
.ConfigureFunctionsWorkerDefaults(builder => {
|
||||||
builder.UseAzureAppConfiguration();
|
builder.UseAzureAppConfiguration();
|
||||||
builder.UseMiddleware<LoggingMiddleware>();
|
builder.UseMiddleware<LoggingMiddleware>();
|
||||||
|
builder.UseMiddleware<Auth.AuthenticationMiddleware>();
|
||||||
|
builder.UseMiddleware<Auth.AuthorizationMiddleware>();
|
||||||
builder.AddApplicationInsights(options => {
|
builder.AddApplicationInsights(options => {
|
||||||
options.ConnectionString = $"InstrumentationKey={configuration.ApplicationInsightsInstrumentationKey}";
|
options.ConnectionString = $"InstrumentationKey={configuration.ApplicationInsightsInstrumentationKey}";
|
||||||
});
|
});
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
|
||||||
|
|
||||||
public interface IUserCredentials {
|
|
||||||
public string? GetBearerToken(HttpRequestData req);
|
|
||||||
public string? GetAuthToken(HttpRequestData req);
|
|
||||||
public Task<OneFuzzResult<UserAuthInfo>> ParseJwtToken(HttpRequestData req);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record UserAuthInfo(UserInfo UserInfo, List<string> Roles);
|
|
||||||
|
|
||||||
public class UserCredentials : IUserCredentials {
|
|
||||||
ILogTracer _log;
|
|
||||||
IConfigOperations _instanceConfig;
|
|
||||||
private JwtSecurityTokenHandler _tokenHandler;
|
|
||||||
|
|
||||||
public UserCredentials(ILogTracer log, IConfigOperations instanceConfig) {
|
|
||||||
_log = log;
|
|
||||||
_instanceConfig = instanceConfig;
|
|
||||||
_tokenHandler = new JwtSecurityTokenHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? GetBearerToken(HttpRequestData req) {
|
|
||||||
if (!req.Headers.TryGetValues("Authorization", out var authHeader) || authHeader.IsNullOrEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
var auth = AuthenticationHeaderValue.Parse(authHeader.First());
|
|
||||||
return auth.Scheme.ToLower() switch {
|
|
||||||
"bearer" => auth.Parameter,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? GetAuthToken(HttpRequestData req) {
|
|
||||||
var token = GetBearerToken(req);
|
|
||||||
if (token is not null) {
|
|
||||||
return token;
|
|
||||||
} else {
|
|
||||||
if (!req.Headers.TryGetValues("x-ms-token-aad-id-token", out var tokenHeader) || tokenHeader.IsNullOrEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return tokenHeader.First();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<OneFuzzResult<string[]>> GetAllowedTenants() {
|
|
||||||
var r = await _instanceConfig.Fetch();
|
|
||||||
var allowedAddTenantsQuery =
|
|
||||||
from t in r.AllowedAadTenants
|
|
||||||
select $"https://sts.windows.net/{t}/";
|
|
||||||
|
|
||||||
return OneFuzzResult<string[]>.Ok(allowedAddTenantsQuery.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<OneFuzzResult<UserAuthInfo>> ParseJwtToken(HttpRequestData req) {
|
|
||||||
|
|
||||||
|
|
||||||
var authToken = GetAuthToken(req);
|
|
||||||
if (authToken is null) {
|
|
||||||
return OneFuzzResult<UserAuthInfo>.Error(ErrorCode.INVALID_REQUEST, new[] { "unable to find authorization token" });
|
|
||||||
} else {
|
|
||||||
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(authToken);
|
|
||||||
var allowedTenants = await GetAllowedTenants();
|
|
||||||
if (allowedTenants.IsOk) {
|
|
||||||
if (allowedTenants.OkV is not null && allowedTenants.OkV.Contains(token.Issuer)) {
|
|
||||||
var userAuthInfo = new UserAuthInfo(new UserInfo(null, null, null), new List<string>());
|
|
||||||
var userInfo =
|
|
||||||
token.Payload.Claims.Aggregate(userAuthInfo, (acc, claim) => {
|
|
||||||
switch (claim.Type) {
|
|
||||||
case "oid":
|
|
||||||
return acc with { UserInfo = acc.UserInfo with { ObjectId = Guid.Parse(claim.Value) } };
|
|
||||||
case "appid":
|
|
||||||
return acc with { UserInfo = acc.UserInfo with { ApplicationId = Guid.Parse(claim.Value) } };
|
|
||||||
case "upn":
|
|
||||||
return acc with { UserInfo = acc.UserInfo with { Upn = claim.Value } };
|
|
||||||
case "roles":
|
|
||||||
acc.Roles.Add(claim.Value);
|
|
||||||
return acc;
|
|
||||||
default:
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return OneFuzzResult<UserAuthInfo>.Ok(userInfo);
|
|
||||||
} else {
|
|
||||||
var tenantsStr = allowedTenants.OkV is null ? "null" : String.Join(';', allowedTenants.OkV!);
|
|
||||||
_log.Error($"issuer not from allowed tenant. issuer: {token.Issuer:Tag:Issuer} - tenants: {tenantsStr:Tag:Tenants}");
|
|
||||||
return OneFuzzResult<UserAuthInfo>.Error(ErrorCode.INVALID_REQUEST, new[] { "unauthorized AAD issuer. If multi-tenant auth is failing, make sure to include all tenant_ids in the `allowed_aad_tenants` list in the instance_config. To see the current instance_config, run `onefuzz instance_config get`. " });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_log.Error($"Failed to get allowed tenants due to {allowedTenants.ErrorV:Tag:Error}");
|
|
||||||
return OneFuzzResult<UserAuthInfo>.Error(allowedTenants.ErrorV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
public class Auth {
|
public static class AuthHelpers {
|
||||||
|
|
||||||
private static ProcessStartInfo SshKeyGenProcConfig(string tempFile) {
|
private static ProcessStartInfo SshKeyGenProcConfig(string tempFile) {
|
||||||
|
|
||||||
|
@ -1,36 +1,23 @@
|
|||||||
using System.Net;
|
using System.Net.Http;
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
using Microsoft.Azure.Functions.Worker.Http;
|
||||||
using Microsoft.Graph;
|
using Microsoft.Graph;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
public record UserAuthInfo(UserInfo UserInfo, List<string> Roles);
|
||||||
|
|
||||||
public interface IEndpointAuthorization {
|
public interface IEndpointAuthorization {
|
||||||
|
Async.Task<OneFuzzResultVoid> CheckRequireAdmins(UserAuthInfo authInfo);
|
||||||
Async.Task<HttpResponseData> CallIfAgent(
|
Async.Task<(bool, string)> IsAgent(UserAuthInfo authInfo);
|
||||||
HttpRequestData req,
|
Async.Task<OneFuzzResultVoid> CheckAccess(HttpRequestData req);
|
||||||
Func<HttpRequestData, Async.Task<HttpResponseData>> method)
|
|
||||||
=> CallIf(req, method, allowAgent: true);
|
|
||||||
|
|
||||||
Async.Task<HttpResponseData> CallIfUser(
|
|
||||||
HttpRequestData req,
|
|
||||||
Func<HttpRequestData, Async.Task<HttpResponseData>> method)
|
|
||||||
=> CallIf(req, method, allowUser: true);
|
|
||||||
|
|
||||||
Async.Task<HttpResponseData> CallIf(
|
|
||||||
HttpRequestData req,
|
|
||||||
Func<HttpRequestData, Async.Task<HttpResponseData>> method,
|
|
||||||
bool allowUser = false,
|
|
||||||
bool allowAgent = false);
|
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> CheckRequireAdmins(HttpRequestData req);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class EndpointAuthorization : IEndpointAuthorization {
|
public class EndpointAuthorization : IEndpointAuthorization {
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
private readonly ILogTracer _log;
|
private readonly ILogTracer _log;
|
||||||
private readonly GraphServiceClient _graphClient;
|
private readonly GraphServiceClient _graphClient;
|
||||||
private static readonly HashSet<string> AgentRoles = new HashSet<string> { "UnmanagedNode", "ManagedNode" };
|
private static readonly IReadOnlySet<string> _agentRoles = new HashSet<string>() { "UnmanagedNode", "ManagedNode" };
|
||||||
|
|
||||||
public EndpointAuthorization(IOnefuzzContext context, ILogTracer log, GraphServiceClient graphClient) {
|
public EndpointAuthorization(IOnefuzzContext context, ILogTracer log, GraphServiceClient graphClient) {
|
||||||
_context = context;
|
_context = context;
|
||||||
@ -38,58 +25,7 @@ public class EndpointAuthorization : IEndpointAuthorization {
|
|||||||
_graphClient = graphClient;
|
_graphClient = graphClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Async.Task<HttpResponseData> CallIf(HttpRequestData req, Func<HttpRequestData, Async.Task<HttpResponseData>> method, bool allowUser = false, bool allowAgent = false) {
|
public async Async.Task<OneFuzzResultVoid> CheckRequireAdmins(UserAuthInfo authInfo) {
|
||||||
var tokenResult = await _context.UserCredentials.ParseJwtToken(req);
|
|
||||||
|
|
||||||
if (!tokenResult.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, tokenResult.ErrorV, "token verification", HttpStatusCode.Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = tokenResult.OkV.UserInfo;
|
|
||||||
|
|
||||||
var (isAgent, reason) = await IsAgent(tokenResult.OkV);
|
|
||||||
|
|
||||||
if (!isAgent) {
|
|
||||||
if (!allowUser) {
|
|
||||||
return await Reject(req, token, "endpoint not allowed for users");
|
|
||||||
}
|
|
||||||
|
|
||||||
var access = await CheckAccess(req);
|
|
||||||
if (!access.IsOk) {
|
|
||||||
return await _context.RequestHandling.NotOk(req, access.ErrorV, "access control", HttpStatusCode.Unauthorized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (isAgent && !allowAgent) {
|
|
||||||
return await Reject(req, token, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await method(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Async.Task<HttpResponseData> Reject(HttpRequestData req, UserInfo token, String? reason = null) {
|
|
||||||
var body = await req.ReadAsStringAsync();
|
|
||||||
_log.Error($"reject token. reason:{reason} url:{req.Url:Tag:Url} token:{token:Tag:Token} body:{body:Tag:Body}");
|
|
||||||
|
|
||||||
return await _context.RequestHandling.NotOk(
|
|
||||||
req,
|
|
||||||
Error.Create(
|
|
||||||
ErrorCode.UNAUTHORIZED,
|
|
||||||
reason ?? "Unrecognized agent"
|
|
||||||
),
|
|
||||||
"token verification",
|
|
||||||
HttpStatusCode.Unauthorized
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> CheckRequireAdmins(HttpRequestData req) {
|
|
||||||
var tokenResult = await _context.UserCredentials.ParseJwtToken(req);
|
|
||||||
if (!tokenResult.IsOk) {
|
|
||||||
return tokenResult.ErrorV;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = await _context.ConfigOperations.Fetch();
|
var config = await _context.ConfigOperations.Fetch();
|
||||||
if (config is null) {
|
if (config is null) {
|
||||||
return Error.Create(
|
return Error.Create(
|
||||||
@ -97,7 +33,7 @@ public class EndpointAuthorization : IEndpointAuthorization {
|
|||||||
"no instance configuration found ");
|
"no instance configuration found ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckRequireAdminsImpl(config, tokenResult.OkV.UserInfo);
|
return CheckRequireAdminsImpl(config, authInfo.UserInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OneFuzzResultVoid CheckRequireAdminsImpl(InstanceConfig config, UserInfo userInfo) {
|
private static OneFuzzResultVoid CheckRequireAdminsImpl(InstanceConfig config, UserInfo userInfo) {
|
||||||
@ -177,9 +113,8 @@ public class EndpointAuthorization : IEndpointAuthorization {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Async.Task<(bool, string)> IsAgent(UserAuthInfo authInfo) {
|
public async Async.Task<(bool, string)> IsAgent(UserAuthInfo authInfo) {
|
||||||
if (!AgentRoles.Overlaps(authInfo.Roles)) {
|
if (!_agentRoles.Overlaps(authInfo.Roles)) {
|
||||||
return (false, "no agent role");
|
return (false, "no agent role");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ public interface IOnefuzzContext {
|
|||||||
IStorage Storage { get; }
|
IStorage Storage { get; }
|
||||||
ITaskOperations TaskOperations { get; }
|
ITaskOperations TaskOperations { get; }
|
||||||
ITaskEventOperations TaskEventOperations { get; }
|
ITaskEventOperations TaskEventOperations { get; }
|
||||||
IUserCredentials UserCredentials { get; }
|
|
||||||
IVmOperations VmOperations { get; }
|
IVmOperations VmOperations { get; }
|
||||||
IVmssOperations VmssOperations { get; }
|
IVmssOperations VmssOperations { get; }
|
||||||
IWebhookMessageLogOperations WebhookMessageLogOperations { get; }
|
IWebhookMessageLogOperations WebhookMessageLogOperations { get; }
|
||||||
@ -77,7 +76,6 @@ public class OnefuzzContext : IOnefuzzContext {
|
|||||||
public IContainers Containers => _serviceProvider.GetRequiredService<IContainers>();
|
public IContainers Containers => _serviceProvider.GetRequiredService<IContainers>();
|
||||||
public IReports Reports => _serviceProvider.GetRequiredService<IReports>();
|
public IReports Reports => _serviceProvider.GetRequiredService<IReports>();
|
||||||
public INotificationOperations NotificationOperations => _serviceProvider.GetRequiredService<INotificationOperations>();
|
public INotificationOperations NotificationOperations => _serviceProvider.GetRequiredService<INotificationOperations>();
|
||||||
public IUserCredentials UserCredentials => _serviceProvider.GetRequiredService<IUserCredentials>();
|
|
||||||
public IReproOperations ReproOperations => _serviceProvider.GetRequiredService<IReproOperations>();
|
public IReproOperations ReproOperations => _serviceProvider.GetRequiredService<IReproOperations>();
|
||||||
public IPoolOperations PoolOperations => _serviceProvider.GetRequiredService<IPoolOperations>();
|
public IPoolOperations PoolOperations => _serviceProvider.GetRequiredService<IPoolOperations>();
|
||||||
public IIpOperations IpOperations => _serviceProvider.GetRequiredService<IIpOperations>();
|
public IIpOperations IpOperations => _serviceProvider.GetRequiredService<IIpOperations>();
|
||||||
|
@ -59,7 +59,17 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logTracer.Info($"creating proxy: region:{region:Tag:Region}");
|
_logTracer.Info($"creating proxy: region:{region:Tag:Region}");
|
||||||
var newProxy = new Proxy(region, Guid.NewGuid(), DateTimeOffset.UtcNow, VmState.Init, new SecretValue<Authentication>(await Auth.BuildAuth(_logTracer)), null, null, _context.ServiceConfiguration.OneFuzzVersion, null, false);
|
var newProxy = new Proxy(
|
||||||
|
region,
|
||||||
|
Guid.NewGuid(),
|
||||||
|
DateTimeOffset.UtcNow,
|
||||||
|
VmState.Init,
|
||||||
|
new SecretValue<Authentication>(await AuthHelpers.BuildAuth(_logTracer)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
_context.ServiceConfiguration.OneFuzzVersion,
|
||||||
|
null,
|
||||||
|
false);
|
||||||
|
|
||||||
var r = await Replace(newProxy);
|
var r = await Replace(newProxy);
|
||||||
if (!r.IsOk) {
|
if (!r.IsOk) {
|
||||||
|
@ -335,7 +335,7 @@ public class ReproOperations : StatefulOrm<Repro, VmState, ReproOperations>, IRe
|
|||||||
return OneFuzzResult<Repro>.Error(ErrorCode.INVALID_REQUEST, "unable to find task");
|
return OneFuzzResult<Repro>.Error(ErrorCode.INVALID_REQUEST, "unable to find task");
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = await _context.SecretsOperations.StoreSecret(new SecretValue<Authentication>(await Auth.BuildAuth(_logTracer)));
|
var auth = await _context.SecretsOperations.StoreSecret(new SecretValue<Authentication>(await AuthHelpers.BuildAuth(_logTracer)));
|
||||||
|
|
||||||
var vm = new Repro(
|
var vm = new Repro(
|
||||||
VmId: Guid.NewGuid(),
|
VmId: Guid.NewGuid(),
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
using System.Net;
|
using Microsoft.OneFuzz.Service;
|
||||||
using IntegrationTests.Fakes;
|
|
||||||
using Microsoft.OneFuzz.Service;
|
|
||||||
using Microsoft.OneFuzz.Service.Functions;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Async = System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace IntegrationTests;
|
namespace IntegrationTests;
|
||||||
|
|
||||||
@ -23,31 +19,4 @@ public abstract class AgentCanScheduleTestsBase : FunctionTestBase {
|
|||||||
public AgentCanScheduleTestsBase(ITestOutputHelper output, IStorage storage)
|
public AgentCanScheduleTestsBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Authorization_IsRequired() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new AgentCanSchedule(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task UserAuthorization_IsNotPermitted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new AgentCanSchedule(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task AgentAuthorization_IsAccepted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
|
||||||
var func = new AgentCanSchedule(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); // BadRequest due to no body, not Unauthorized
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,33 +26,6 @@ public abstract class AgentCommandsTestsBase : FunctionTestBase {
|
|||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Authorization_IsRequired() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new AgentCommands(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task UserAuthorization_IsNotPermitted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new AgentCommands(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task AgentAuthorization_IsAccepted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
|
||||||
var func = new AgentCommands(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); // BadRequest due to no body, not Unauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task AgentCommand_GetsCommand() {
|
public async Async.Task AgentCommand_GetsCommand() {
|
||||||
var machineId = Guid.NewGuid();
|
var machineId = Guid.NewGuid();
|
||||||
@ -69,8 +42,7 @@ public abstract class AgentCommandsTestsBase : FunctionTestBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var commandRequest = new NodeCommandGet(machineId);
|
var commandRequest = new NodeCommandGet(machineId);
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentCommands(Logger, Context);
|
||||||
var func = new AgentCommands(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", commandRequest));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", commandRequest));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
@ -35,28 +35,9 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
readonly Guid _poolId = Guid.NewGuid();
|
readonly Guid _poolId = Guid.NewGuid();
|
||||||
readonly string _poolVersion = $"version-{Guid.NewGuid()}";
|
readonly string _poolVersion = $"version-{Guid.NewGuid()}";
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Authorization_IsRequired() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task UserAuthorization_IsNotPermitted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task WorkerEventMustHaveDoneOrRunningSet() {
|
public async Async.Task WorkerEventMustHaveDoneOrRunningSet() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: Guid.NewGuid(),
|
MachineId: Guid.NewGuid(),
|
||||||
@ -75,8 +56,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
||||||
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
@ -103,8 +83,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
||||||
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
@ -130,8 +109,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
new Task(_jobId, _taskId, TaskState.Scheduled, Os.Linux,
|
new Task(_jobId, _taskId, TaskState.Scheduled, Os.Linux,
|
||||||
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 100))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
@ -156,8 +134,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, _poolId, _poolVersion));
|
new Node(_poolName, _machineId, _poolId, _poolVersion));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
||||||
@ -173,8 +150,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
||||||
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 0))));
|
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 0))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
||||||
@ -191,8 +167,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
new Task(_jobId, _taskId, TaskState.Running, Os.Linux,
|
||||||
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 0))));
|
new TaskConfig(_jobId, null, new TaskDetails(TaskType.Coverage, 0))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
Event: new WorkerEvent(Running: new WorkerRunningEvent(_taskId)));
|
||||||
@ -232,8 +207,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
public async Async.Task NodeStateUpdate_ForMissingNode_IgnoresEvent() {
|
public async Async.Task NodeStateUpdate_ForMissingNode_IgnoresEvent() {
|
||||||
// nothing present in storage
|
// nothing present in storage
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new NodeStateUpdate(NodeState.Init));
|
Event: new NodeStateUpdate(NodeState.Init));
|
||||||
@ -248,8 +222,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, _poolId, _poolVersion, State: NodeState.Init));
|
new Node(_poolName, _machineId, _poolId, _poolVersion, State: NodeState.Init));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new NodeStateUpdate(NodeState.Ready));
|
Event: new NodeStateUpdate(NodeState.Ready));
|
||||||
@ -266,8 +239,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, _poolId, _poolVersion, ReimageRequested: true));
|
new Node(_poolName, _machineId, _poolId, _poolVersion, ReimageRequested: true));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new NodeStateUpdate(NodeState.Free));
|
Event: new NodeStateUpdate(NodeState.Free));
|
||||||
@ -295,8 +267,7 @@ public abstract class AgentEventsTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, _poolId, _poolVersion, DeleteRequested: true));
|
new Node(_poolName, _machineId, _poolId, _poolVersion, DeleteRequested: true));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentEvents(Logger, Context);
|
||||||
var func = new AgentEvents(Logger, auth, Context);
|
|
||||||
var data = new NodeStateEnvelope(
|
var data = new NodeStateEnvelope(
|
||||||
MachineId: _machineId,
|
MachineId: _machineId,
|
||||||
Event: new NodeStateUpdate(NodeState.Free));
|
Event: new NodeStateUpdate(NodeState.Free));
|
||||||
|
@ -33,37 +33,9 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
private readonly ScalesetId _scalesetId = ScalesetId.Parse($"scaleset-{Guid.NewGuid()}");
|
private readonly ScalesetId _scalesetId = ScalesetId.Parse($"scaleset-{Guid.NewGuid()}");
|
||||||
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Authorization_IsRequired() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task UserAuthorization_IsNotPermitted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task AgentAuthorization_IsAccepted() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("POST"));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); // BadRequest due to missing parameters, not Unauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Get_UrlParameterRequired() {
|
public async Async.Task Get_UrlParameterRequired() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("GET");
|
var req = TestHttpRequestData.Empty("GET");
|
||||||
var result = await func.Run(req);
|
var result = await func.Run(req);
|
||||||
@ -76,8 +48,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Get_MissingNode() {
|
public async Async.Task Get_MissingNode() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("GET");
|
var req = TestHttpRequestData.Empty("GET");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -95,8 +66,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, _poolId, "1.0.0"));
|
new Node(_poolName, _machineId, _poolId, "1.0.0"));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("GET");
|
var req = TestHttpRequestData.Empty("GET");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -115,8 +85,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
new Node(_poolName, _machineId, _poolId, "1.0.0"),
|
new Node(_poolName, _machineId, _poolId, "1.0.0"),
|
||||||
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("GET");
|
var req = TestHttpRequestData.Empty("GET");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -135,8 +104,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("POST");
|
var req = TestHttpRequestData.Empty("POST");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -157,8 +125,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("POST");
|
var req = TestHttpRequestData.Empty("POST");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -181,8 +148,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
new Node(PoolName.Parse("another-pool"), _machineId, _poolId, "1.0.0"),
|
new Node(PoolName.Parse("another-pool"), _machineId, _poolId, "1.0.0"),
|
||||||
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("POST");
|
var req = TestHttpRequestData.Empty("POST");
|
||||||
req.SetUrlParameter("machine_id", _machineId);
|
req.SetUrlParameter("machine_id", _machineId);
|
||||||
@ -205,8 +171,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
new Pool(_poolName, _poolId, Os.Linux, false, Architecture.x86_64, PoolState.Init, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
var func = new AgentRegistration(Logger, Context);
|
||||||
var func = new AgentRegistration(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = TestHttpRequestData.Empty("POST");
|
var req = TestHttpRequestData.Empty("POST");
|
||||||
if (parameterToSkip != "machine_id") {
|
if (parameterToSkip != "machine_id") {
|
||||||
|
@ -14,7 +14,7 @@ namespace Tests {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async System.Threading.Tasks.Task TestAuth() {
|
public async System.Threading.Tasks.Task TestAuth() {
|
||||||
var auth = await Microsoft.OneFuzz.Service.Auth.BuildAuth(Logger);
|
var auth = await AuthHelpers.BuildAuth(Logger);
|
||||||
|
|
||||||
auth.Should().NotBeNull();
|
auth.Should().NotBeNull();
|
||||||
auth.PrivateKey.StartsWith("-----BEGIN OPENSSH PRIVATE KEY-----").Should().BeTrue();
|
auth.PrivateKey.StartsWith("-----BEGIN OPENSSH PRIVATE KEY-----").Should().BeTrue();
|
||||||
|
@ -31,22 +31,6 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
public ContainersTestBase(ITestOutputHelper output, IStorage storage)
|
public ContainersTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("GET")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task WithoutAuthorization_IsRejected(string method) {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty(method));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(ErrorCode.UNAUTHORIZED.ToString(), err.Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CanDelete() {
|
public async Async.Task CanDelete() {
|
||||||
var containerName = Container.Parse("test");
|
var containerName = Container.Parse("test");
|
||||||
@ -55,8 +39,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var msg = TestHttpRequestData.FromJson("DELETE", new ContainerDelete(containerName));
|
var msg = TestHttpRequestData.FromJson("DELETE", new ContainerDelete(containerName));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -71,8 +54,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
var containerName = Container.Parse("test");
|
var containerName = Container.Parse("test");
|
||||||
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, meta));
|
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, meta));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -95,8 +77,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
var metadata = new Dictionary<string, string> { { "some", "value" } };
|
var metadata = new Dictionary<string, string> { { "some", "value" } };
|
||||||
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, metadata));
|
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, metadata));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -119,8 +100,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(containerName));
|
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(containerName));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -134,8 +114,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
var container = Container.Parse("container");
|
var container = Container.Parse("container");
|
||||||
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(container));
|
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(container));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
}
|
}
|
||||||
@ -149,8 +128,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var msg = TestHttpRequestData.Empty("GET"); // this means list all
|
var msg = TestHttpRequestData.Empty("GET"); // this means list all
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -188,8 +166,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
// use anonymous type so we can send an invalid name
|
// use anonymous type so we can send an invalid name
|
||||||
var msg = TestHttpRequestData.FromJson("POST", new { Name = "AbCd" });
|
var msg = TestHttpRequestData.FromJson("POST", new { Name = "AbCd" });
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ContainersFunction(Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
|
||||||
|
@ -26,26 +26,13 @@ public abstract class DownloadTestBase : FunctionTestBase {
|
|||||||
public DownloadTestBase(ITestOutputHelper output, IStorage storage)
|
public DownloadTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Download_WithoutAuthorization_IsRejected() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new Download(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(ErrorCode.UNAUTHORIZED.ToString(), err.Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Download_WithoutContainer_IsRejected() {
|
public async Async.Task Download_WithoutContainer_IsRejected() {
|
||||||
var req = TestHttpRequestData.Empty("GET");
|
var req = TestHttpRequestData.Empty("GET");
|
||||||
var url = new UriBuilder(req.Url) { Query = "filename=xxx" }.Uri;
|
var url = new UriBuilder(req.Url) { Query = "filename=xxx" }.Uri;
|
||||||
req.SetUrl(url);
|
req.SetUrl(url);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Download(Context);
|
||||||
var func = new Download(auth, Context);
|
|
||||||
var result = await func.Run(req);
|
var result = await func.Run(req);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
|
||||||
@ -59,8 +46,7 @@ public abstract class DownloadTestBase : FunctionTestBase {
|
|||||||
var url = new UriBuilder(req.Url) { Query = "container=xxx" }.Uri;
|
var url = new UriBuilder(req.Url) { Query = "container=xxx" }.Uri;
|
||||||
req.SetUrl(url);
|
req.SetUrl(url);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Download(Context);
|
||||||
var func = new Download(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(req);
|
var result = await func.Run(req);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
@ -81,8 +67,7 @@ public abstract class DownloadTestBase : FunctionTestBase {
|
|||||||
var url = new UriBuilder(req.Url) { Query = "container=xxx&filename=yyy" }.Uri;
|
var url = new UriBuilder(req.Url) { Query = "container=xxx&filename=yyy" }.Uri;
|
||||||
req.SetUrl(url);
|
req.SetUrl(url);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Download(Context);
|
||||||
var func = new Download(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(req);
|
var result = await func.Run(req);
|
||||||
Assert.Equal(HttpStatusCode.Found, result.StatusCode);
|
Assert.Equal(HttpStatusCode.Found, result.StatusCode);
|
||||||
|
93
src/ApiService/IntegrationTests/EndpointAuthTests.cs
Normal file
93
src/ApiService/IntegrationTests/EndpointAuthTests.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.OneFuzz.Service;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Async = System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IntegrationTests;
|
||||||
|
|
||||||
|
|
||||||
|
[Trait("Category", "Live")]
|
||||||
|
public class AzureStorageEndpointAuthTest : EndpointAuthTestBase {
|
||||||
|
public AzureStorageEndpointAuthTest(ITestOutputHelper output)
|
||||||
|
: base(output, Integration.AzureStorage.FromEnvironment()) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AzuriteEndpointAuthTest : EndpointAuthTestBase {
|
||||||
|
public AzuriteEndpointAuthTest(ITestOutputHelper output)
|
||||||
|
: base(output, new Integration.AzuriteStorage()) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class EndpointAuthTestBase : FunctionTestBase {
|
||||||
|
public EndpointAuthTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
|
: base(output, storage) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Guid _applicationId = Guid.NewGuid();
|
||||||
|
private readonly Guid _userObjectId = Guid.NewGuid();
|
||||||
|
|
||||||
|
private Task<OneFuzzResultVoid> CheckUserAdmin() {
|
||||||
|
var userAuthInfo = new UserAuthInfo(
|
||||||
|
new UserInfo(ApplicationId: _applicationId, ObjectId: _userObjectId, "upn"),
|
||||||
|
new List<string>());
|
||||||
|
|
||||||
|
var auth = new EndpointAuthorization(Context, Logger, null!);
|
||||||
|
|
||||||
|
return auth.CheckRequireAdmins(userAuthInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task IfRequireAdminPrivilegesIsEnabled_UserIsNotPermitted() {
|
||||||
|
// config specifies that a different user is admin
|
||||||
|
await Context.InsertAll(
|
||||||
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
||||||
|
RequireAdminPrivileges = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await CheckUserAdmin();
|
||||||
|
Assert.False(result.IsOk, "should not be admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task IfRequireAdminPrivilegesIsDisabled_UserIsPermitted() {
|
||||||
|
// disable requiring admin privileges
|
||||||
|
await Context.InsertAll(
|
||||||
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
||||||
|
RequireAdminPrivileges = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await CheckUserAdmin();
|
||||||
|
Assert.True(result.IsOk, "should be admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task EnablingAdminForAnotherUserDoesNotPermitThisUser() {
|
||||||
|
var otherUserObjectId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// config specifies that a different user is admin
|
||||||
|
await Context.InsertAll(
|
||||||
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
||||||
|
Admins = new[] { otherUserObjectId },
|
||||||
|
RequireAdminPrivileges = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await CheckUserAdmin();
|
||||||
|
Assert.False(result.IsOk, "should not be admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task UserCanBeAdmin() {
|
||||||
|
// config specifies that user is admin
|
||||||
|
await Context.InsertAll(
|
||||||
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
||||||
|
Admins = new[] { _userObjectId },
|
||||||
|
RequireAdminPrivileges = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await CheckUserAdmin();
|
||||||
|
Assert.True(result.IsOk, "should be admin");
|
||||||
|
}
|
||||||
|
}
|
@ -46,8 +46,7 @@ public abstract class EventsTestBase : FunctionTestBase {
|
|||||||
ping.Should().NotBeNull();
|
ping.Should().NotBeNull();
|
||||||
|
|
||||||
var msg = TestHttpRequestData.FromJson("GET", new EventsGet(ping.PingId));
|
var msg = TestHttpRequestData.FromJson("GET", new EventsGet(ping.PingId));
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new EventsFunction(Logger, Context);
|
||||||
var func = new EventsFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(msg);
|
var result = await func.Run(msg);
|
||||||
result.StatusCode.Should().Be(HttpStatusCode.OK);
|
result.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
ScalesetOperations = new ScalesetOperations(logTracer, cache, this);
|
ScalesetOperations = new ScalesetOperations(logTracer, cache, this);
|
||||||
ReproOperations = new ReproOperations(logTracer, this);
|
ReproOperations = new ReproOperations(logTracer, this);
|
||||||
Reports = new Reports(logTracer, Containers);
|
Reports = new Reports(logTracer, Containers);
|
||||||
UserCredentials = new UserCredentials(logTracer, ConfigOperations);
|
|
||||||
NotificationOperations = new NotificationOperations(logTracer, this);
|
NotificationOperations = new NotificationOperations(logTracer, this);
|
||||||
|
|
||||||
FeatureManagerSnapshot = new TestFeatureManagerSnapshot();
|
FeatureManagerSnapshot = new TestFeatureManagerSnapshot();
|
||||||
@ -79,7 +78,6 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
public ICreds Creds { get; }
|
public ICreds Creds { get; }
|
||||||
public IContainers Containers { get; set; }
|
public IContainers Containers { get; set; }
|
||||||
public IQueue Queue { get; }
|
public IQueue Queue { get; }
|
||||||
public IUserCredentials UserCredentials { get; set; }
|
|
||||||
|
|
||||||
public IRequestHandling RequestHandling { get; }
|
public IRequestHandling RequestHandling { get; }
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
|
||||||
using Microsoft.OneFuzz.Service;
|
|
||||||
|
|
||||||
namespace IntegrationTests.Fakes;
|
|
||||||
|
|
||||||
public enum RequestType {
|
|
||||||
NoAuthorization,
|
|
||||||
User,
|
|
||||||
Agent,
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class TestEndpointAuthorization : EndpointAuthorization {
|
|
||||||
private readonly RequestType _type;
|
|
||||||
private readonly IOnefuzzContext _context;
|
|
||||||
|
|
||||||
public TestEndpointAuthorization(RequestType type, ILogTracer log, IOnefuzzContext context)
|
|
||||||
: base(context, log, null! /* not needed for test */) {
|
|
||||||
_type = type;
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<HttpResponseData> CallIf(
|
|
||||||
HttpRequestData req,
|
|
||||||
Func<HttpRequestData, Task<HttpResponseData>> method,
|
|
||||||
bool allowUser = false,
|
|
||||||
bool allowAgent = false) {
|
|
||||||
|
|
||||||
if ((_type == RequestType.User && allowUser) ||
|
|
||||||
(_type == RequestType.Agent && allowAgent)) {
|
|
||||||
return method(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _context.RequestHandling.NotOk(
|
|
||||||
req,
|
|
||||||
Error.Create(
|
|
||||||
ErrorCode.UNAUTHORIZED,
|
|
||||||
"Unrecognized agent"
|
|
||||||
),
|
|
||||||
"token verification",
|
|
||||||
HttpStatusCode.Unauthorized
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
32
src/ApiService/IntegrationTests/Fakes/TestFunctionContext.cs
Normal file
32
src/ApiService/IntegrationTests/Fakes/TestFunctionContext.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
using Microsoft.OneFuzz.Service;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
|
||||||
|
namespace IntegrationTests.Fakes;
|
||||||
|
|
||||||
|
public class TestFunctionContext : FunctionContext {
|
||||||
|
public override IDictionary<object, object> Items { get; set; } = new Dictionary<object, object>();
|
||||||
|
|
||||||
|
public void SetUserAuthInfo(UserInfo userInfo)
|
||||||
|
=> this.SetUserAuthInfo(new UserAuthInfo(userInfo, new List<string>()));
|
||||||
|
|
||||||
|
// everything else unsupported
|
||||||
|
|
||||||
|
public override string InvocationId => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override string FunctionId => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override TraceContext TraceContext => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override BindingContext BindingContext => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override RetryContext RetryContext => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override IServiceProvider InstanceServices { get => throw new NotSupportedException(); set => throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public override FunctionDefinition FunctionDefinition => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override IInvocationFeatures Features => throw new NotSupportedException();
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Azure.Functions.Worker.Http;
|
|
||||||
using Microsoft.OneFuzz.Service;
|
|
||||||
|
|
||||||
using Async = System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace IntegrationTests.Fakes;
|
|
||||||
|
|
||||||
sealed class TestUserCredentials : UserCredentials {
|
|
||||||
|
|
||||||
private readonly OneFuzzResult<UserAuthInfo> _tokenResult;
|
|
||||||
|
|
||||||
public TestUserCredentials(ILogTracer log, IConfigOperations instanceConfig, OneFuzzResult<UserInfo> tokenResult)
|
|
||||||
: base(log, instanceConfig) {
|
|
||||||
_tokenResult = tokenResult.IsOk ? OneFuzzResult<UserAuthInfo>.Ok(new UserAuthInfo(tokenResult.OkV, new List<string>())) : OneFuzzResult<UserAuthInfo>.Error(tokenResult.ErrorV);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<OneFuzzResult<UserAuthInfo>> ParseJwtToken(HttpRequestData req) => Async.Task.FromResult(_tokenResult);
|
|
||||||
}
|
|
@ -26,27 +26,8 @@ public abstract class InfoTestBase : FunctionTestBase {
|
|||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task TestInfo_WithoutAuthorization_IsRejected() {
|
public async Async.Task TestInfo_Succeeds() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
var func = new Info(Context);
|
||||||
var func = new Info(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task TestInfo_WithAgentCredentials_IsRejected() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.Agent, Logger, Context);
|
|
||||||
var func = new Info(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task TestInfo_WithUserCredentials_Succeeds() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new Info(auth, Context);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
var result = await func.Run(TestHttpRequestData.Empty("GET"));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
@ -24,8 +24,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Dry_Run_Does_Not_Make_Changes() {
|
public async Async.Task Dry_Run_Does_Not_Make_Changes() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
||||||
var r = await Context.NotificationOperations.Create(
|
var r = await Context.NotificationOperations.Create(
|
||||||
@ -39,8 +37,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var notificationBefore = r.OkV!;
|
var notificationBefore = r.OkV!;
|
||||||
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost(DryRun: true);
|
var req = new JinjaToScribanMigrationPost(DryRun: true);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -62,8 +59,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Migration_Happens_When_Not_Dry_run() {
|
public async Async.Task Migration_Happens_When_Not_Dry_run() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
||||||
var r = await Context.NotificationOperations.Create(
|
var r = await Context.NotificationOperations.Create(
|
||||||
@ -77,8 +72,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var notificationBefore = r.OkV!;
|
var notificationBefore = r.OkV!;
|
||||||
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
var req = new JinjaToScribanMigrationPost();
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -99,8 +93,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task OptionalFieldsAreSupported() {
|
public async Async.Task OptionalFieldsAreSupported() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var adoTemplate = new AdoTemplate(
|
var adoTemplate = new AdoTemplate(
|
||||||
new Uri("http://example.com"),
|
new Uri("http://example.com"),
|
||||||
new SecretData<string>(new SecretValue<string>("some secret")),
|
new SecretData<string>(new SecretValue<string>("some secret")),
|
||||||
@ -126,8 +118,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task All_ADO_Fields_Are_Migrated() {
|
public async Async.Task All_ADO_Fields_Are_Migrated() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
var adoTemplate = new AdoTemplate(
|
var adoTemplate = new AdoTemplate(
|
||||||
new Uri("http://example.com"),
|
new Uri("http://example.com"),
|
||||||
@ -161,8 +151,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var notificationBefore = r.OkV!;
|
var notificationBefore = r.OkV!;
|
||||||
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
var adoTemplateBefore = (notificationBefore.Config as AdoTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
var req = new JinjaToScribanMigrationPost();
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -196,8 +185,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task All_Github_Fields_Are_Migrated() {
|
public async Async.Task All_Github_Fields_Are_Migrated() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var githubTemplate = MigratableGithubTemplate();
|
var githubTemplate = MigratableGithubTemplate();
|
||||||
|
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
@ -213,8 +200,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var notificationBefore = r.OkV!;
|
var notificationBefore = r.OkV!;
|
||||||
var githubTemplateBefore = (notificationBefore.Config as GithubIssuesTemplate)!;
|
var githubTemplateBefore = (notificationBefore.Config as GithubIssuesTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
var req = new JinjaToScribanMigrationPost();
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -252,8 +238,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Teams_Template_Not_Migrated() {
|
public async Async.Task Teams_Template_Not_Migrated() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var teamsTemplate = GetTeamsTemplate();
|
var teamsTemplate = GetTeamsTemplate();
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
||||||
@ -268,8 +252,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var notificationBefore = r.OkV!;
|
var notificationBefore = r.OkV!;
|
||||||
var teamsTemplateBefore = (notificationBefore.Config as TeamsTemplate)!;
|
var teamsTemplateBefore = (notificationBefore.Config as TeamsTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
var req = new JinjaToScribanMigrationPost();
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -288,8 +271,6 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
// Multiple notification configs can be migrated
|
// Multiple notification configs can be migrated
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Can_Migrate_Multiple_Notification_Configs() {
|
public async Async.Task Can_Migrate_Multiple_Notification_Configs() {
|
||||||
await ConfigureAuth();
|
|
||||||
|
|
||||||
var notificationContainer = Container.Parse("abc123");
|
var notificationContainer = Container.Parse("abc123");
|
||||||
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
var _ = await Context.Containers.CreateContainer(notificationContainer, StorageType.Corpus, null);
|
||||||
|
|
||||||
@ -323,8 +304,7 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
var githubNotificationBefore = r.OkV!;
|
var githubNotificationBefore = r.OkV!;
|
||||||
var githubTemplateBefore = (githubNotificationBefore.Config as GithubIssuesTemplate)!;
|
var githubTemplateBefore = (githubNotificationBefore.Config as GithubIssuesTemplate)!;
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new JinjaToScribanMigrationFunction(Logger, Context);
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
var req = new JinjaToScribanMigrationPost();
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
@ -351,33 +331,12 @@ public abstract class JinjaToScribanMigrationTestBase : FunctionTestBase {
|
|||||||
githubTemplateAfter.Organization.Should().BeEquivalentTo(JinjaTemplateAdapter.AdaptForScriban(githubTemplateBefore.Organization));
|
githubTemplateAfter.Organization.Should().BeEquivalentTo(JinjaTemplateAdapter.AdaptForScriban(githubTemplateBefore.Organization));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task Access_WithoutAuthorization_IsRejected() {
|
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new JinjaToScribanMigrationFunction(Logger, auth, Context);
|
|
||||||
var req = new JinjaToScribanMigrationPost();
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
|
||||||
|
|
||||||
result.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Do_Not_Enforce_Key_Exists_In_Strict_Validation() {
|
public async Async.Task Do_Not_Enforce_Key_Exists_In_Strict_Validation() {
|
||||||
(await JinjaTemplateAdapter.IsValidScribanNotificationTemplate(Context, Logger, ValidScribanAdoTemplate()))
|
(await JinjaTemplateAdapter.IsValidScribanNotificationTemplate(Context, Logger, ValidScribanAdoTemplate()))
|
||||||
.Should().BeTrue();
|
.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Async.Task ConfigureAuth() {
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } } // needed for admin check
|
|
||||||
);
|
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AdoTemplate MigratableAdoTemplate() {
|
private static AdoTemplate MigratableAdoTemplate() {
|
||||||
return new AdoTemplate(
|
return new AdoTemplate(
|
||||||
new Uri("http://example.com"),
|
new Uri("http://example.com"),
|
||||||
|
@ -29,27 +29,12 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
private readonly Guid _jobId = Guid.NewGuid();
|
private readonly Guid _jobId = Guid.NewGuid();
|
||||||
private readonly JobConfig _config = new("project", "name", "build", 1000, null);
|
private readonly JobConfig _config = new("project", "name", "build", 1000, null);
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("GET")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task Access_WithoutAuthorization_IsRejected(string method) {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.NoAuthorization, Logger, Context);
|
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty(method));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(ErrorCode.UNAUTHORIZED.ToString(), err.Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Delete_NonExistentJob_Fails() {
|
public async Async.Task Delete_NonExistentJob_Fails() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)), ctx);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
@ -61,10 +46,10 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Job(_jobId, JobState.Enabled, _config));
|
new Job(_jobId, JobState.Enabled, _config));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var response = BodyAs<JobResponse>(result);
|
var response = BodyAs<JobResponse>(result);
|
||||||
@ -80,10 +65,10 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Job(_jobId, JobState.Stopped, _config));
|
new Job(_jobId, JobState.Stopped, _config));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", new JobGet(_jobId)), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var response = BodyAs<JobResponse>(result);
|
var response = BodyAs<JobResponse>(result);
|
||||||
@ -100,10 +85,10 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Job(_jobId, JobState.Stopped, _config));
|
new Job(_jobId, JobState.Stopped, _config));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId)));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId)), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var response = BodyAs<JobResponse>(result);
|
var response = BodyAs<JobResponse>(result);
|
||||||
@ -119,11 +104,11 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
new Job(Guid.NewGuid(), JobState.Enabled, _config),
|
new Job(Guid.NewGuid(), JobState.Enabled, _config),
|
||||||
new Job(Guid.NewGuid(), JobState.Stopped, _config));
|
new Job(Guid.NewGuid(), JobState.Stopped, _config));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var req = new JobSearch(State: new List<JobState> { JobState.Enabled });
|
var req = new JobSearch(State: new List<JobState> { JobState.Enabled });
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var response = BodyAs<JobResponse[]>(result);
|
var response = BodyAs<JobResponse[]>(result);
|
||||||
@ -138,11 +123,11 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
new Job(Guid.NewGuid(), JobState.Enabled, _config),
|
new Job(Guid.NewGuid(), JobState.Enabled, _config),
|
||||||
new Job(Guid.NewGuid(), JobState.Stopped, _config));
|
new Job(Guid.NewGuid(), JobState.Stopped, _config));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
var req = new JobSearch(State: new List<JobState> { JobState.Enabled, JobState.Stopping });
|
var req = new JobSearch(State: new List<JobState> { JobState.Enabled, JobState.Stopping });
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var response = BodyAs<JobResponse[]>(result);
|
var response = BodyAs<JobResponse[]>(result);
|
||||||
@ -153,14 +138,12 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Post_CreatesJob_AndContainer() {
|
public async Async.Task Post_CreatesJob_AndContainer() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Jobs(Context, Logger);
|
||||||
var func = new Jobs(auth, Context, Logger);
|
|
||||||
|
|
||||||
// need user credentials to put into the job object
|
// need user credentials to put into the job object
|
||||||
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
var ctx = new TestFunctionContext();
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
ctx.SetUserAuthInfo(new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn"));
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", _config), ctx);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", _config));
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var job = Assert.Single(await Context.JobOperations.SearchAll().ToListAsync());
|
var job = Assert.Single(await Context.JobOperations.SearchAll().ToListAsync());
|
||||||
|
@ -35,10 +35,8 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_SpecificNode_NotFound_ReturnsNotFound() {
|
public async Async.Task Search_SpecificNode_NotFound_ReturnsNotFound() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new NodeSearch(MachineId: _machineId);
|
var req = new NodeSearch(MachineId: _machineId);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
}
|
}
|
||||||
@ -48,10 +46,8 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new Node(_poolName, _machineId, null, _version));
|
new Node(_poolName, _machineId, null, _version));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new NodeSearch(MachineId: _machineId);
|
var req = new NodeSearch(MachineId: _machineId);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -62,10 +58,8 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_MultipleNodes_CanFindNone() {
|
public async Async.Task Search_MultipleNodes_CanFindNone() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new NodeSearch();
|
var req = new NodeSearch();
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
Assert.Equal("[]", BodyAsString(result));
|
Assert.Equal("[]", BodyAsString(result));
|
||||||
@ -80,8 +74,7 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var req = new NodeSearch(PoolName: _poolName);
|
var req = new NodeSearch(PoolName: _poolName);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -99,8 +92,7 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var req = new NodeSearch(ScalesetId: _scalesetId);
|
var req = new NodeSearch(ScalesetId: _scalesetId);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -109,7 +101,6 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
Assert.Equal(2, deserialized.Length);
|
Assert.Equal(2, deserialized.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_MultipleNodes_ByState() {
|
public async Async.Task Search_MultipleNodes_ByState() {
|
||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
@ -119,8 +110,7 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var req = new NodeSearch(State: new List<NodeState> { NodeState.Busy });
|
var req = new NodeSearch(State: new List<NodeState> { NodeState.Busy });
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -139,8 +129,7 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var req = new NodeSearch(State: new List<NodeState> { NodeState.Free, NodeState.Busy });
|
var req = new NodeSearch(State: new List<NodeState> { NodeState.Free, NodeState.Busy });
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new NodeFunction(Logger, Context);
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -148,147 +137,4 @@ public abstract class NodeTestBase : FunctionTestBase {
|
|||||||
var deserialized = BodyAs<NodeSearchResult[]>(result);
|
var deserialized = BodyAs<NodeSearchResult[]>(result);
|
||||||
Assert.Equal(3, deserialized.Length);
|
Assert.Equal(3, deserialized.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("PATCH")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task RequiresAdmin(string method) {
|
|
||||||
// config must be found
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
|
||||||
RequireAdminPrivileges = true
|
|
||||||
});
|
|
||||||
|
|
||||||
// must be a user to auth
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: Guid.NewGuid(), "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var req = new NodeGet(MachineId: _machineId);
|
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson(method, req));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(ErrorCode.UNAUTHORIZED.ToString(), err.Title);
|
|
||||||
Assert.Contains("pool modification disabled", err.Detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("PATCH")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task RequiresAdmin_CanBeDisabled(string method) {
|
|
||||||
// disable requiring admin privileges
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
|
||||||
RequireAdminPrivileges = false
|
|
||||||
});
|
|
||||||
|
|
||||||
// must be a user to auth
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: Guid.NewGuid(), "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var req = new NodeGet(MachineId: _machineId);
|
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson(method, req));
|
|
||||||
|
|
||||||
// we will fail with BadRequest but due to not being able to find the Node,
|
|
||||||
// not because of UNAUTHORIZED
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
|
||||||
Assert.Equal(ErrorCode.UNABLE_TO_FIND.ToString(), BodyAs<ProblemDetails>(result).Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("PATCH")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task UserCanBeAdmin(string method) {
|
|
||||||
var userObjectId = Guid.NewGuid();
|
|
||||||
|
|
||||||
// config specifies that user is admin
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
|
||||||
Admins = new[] { userObjectId }
|
|
||||||
});
|
|
||||||
|
|
||||||
// must be a user to auth
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var req = new NodeGet(MachineId: _machineId);
|
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson(method, req));
|
|
||||||
|
|
||||||
// we will fail with BadRequest but due to not being able to find the Node,
|
|
||||||
// not because of UNAUTHORIZED
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
|
||||||
Assert.Equal(ErrorCode.UNABLE_TO_FIND.ToString(), BodyAs<ProblemDetails>(result).Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("PATCH")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task EnablingAdminForAnotherUserDoesNotPermitThisUser(string method) {
|
|
||||||
var userObjectId = Guid.NewGuid();
|
|
||||||
var otherObjectId = Guid.NewGuid();
|
|
||||||
|
|
||||||
// config specifies that a different user is admin
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
|
||||||
Admins = new[] { otherObjectId }, RequireAdminPrivileges = true
|
|
||||||
});
|
|
||||||
|
|
||||||
// must be a user to auth
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var req = new NodeGet(MachineId: _machineId);
|
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson(method, req));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
|
||||||
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(ErrorCode.UNAUTHORIZED.ToString(), err.Title);
|
|
||||||
Assert.Contains("not authorized to manage instance", err.Detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("PATCH")]
|
|
||||||
[InlineData("POST")]
|
|
||||||
[InlineData("DELETE")]
|
|
||||||
public async Async.Task CanPerformOperation(string method) {
|
|
||||||
// disable requiring admin privileges
|
|
||||||
await Context.InsertAll(
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) {
|
|
||||||
RequireAdminPrivileges = false
|
|
||||||
},
|
|
||||||
new Node(_poolName, _machineId, null, _version));
|
|
||||||
|
|
||||||
// must be a user to auth
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: Guid.NewGuid(), "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
// all of these operations use NodeGet
|
|
||||||
var req = new NodeGet(MachineId: _machineId);
|
|
||||||
var func = new NodeFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson(method, req));
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,26 +31,10 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
private readonly PoolName _poolName = PoolName.Parse("pool-" + Guid.NewGuid());
|
private readonly PoolName _poolName = PoolName.Parse("pool-" + Guid.NewGuid());
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("POST", RequestType.Agent)]
|
|
||||||
[InlineData("POST", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("GET", RequestType.Agent)]
|
|
||||||
[InlineData("GET", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("DELETE", RequestType.Agent)]
|
|
||||||
[InlineData("DELETE", RequestType.NoAuthorization)]
|
|
||||||
public async Async.Task UserAuthorization_IsRequired(string method, RequestType authType) {
|
|
||||||
var auth = new TestEndpointAuthorization(authType, Logger, Context);
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty(method));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_SpecificPool_ById_NotFound_ReturnsBadRequest() {
|
public async Async.Task Search_SpecificPool_ById_NotFound_ReturnsBadRequest() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new PoolSearch(PoolId: _poolId);
|
var req = new PoolSearch(PoolId: _poolId);
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
var func = new PoolFunction(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
}
|
}
|
||||||
@ -66,10 +50,8 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
// use test class to override instance ID
|
// use test class to override instance ID
|
||||||
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new PoolSearch(PoolId: _poolId);
|
var req = new PoolSearch(PoolId: _poolId);
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
var func = new PoolFunction(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -79,10 +61,8 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_SpecificPool_ByName_NotFound_ReturnsBadRequest() {
|
public async Async.Task Search_SpecificPool_ByName_NotFound_ReturnsBadRequest() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new PoolSearch(Name: _poolName);
|
var req = new PoolSearch(Name: _poolName);
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
var func = new PoolFunction(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
}
|
}
|
||||||
@ -98,10 +78,8 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
// use test class to override instance ID
|
// use test class to override instance ID
|
||||||
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new PoolSearch(Name: _poolName);
|
var req = new PoolSearch(Name: _poolName);
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
var func = new PoolFunction(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -111,10 +89,8 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_SpecificPool_ByState_NotFound_ReturnsEmptyResult() {
|
public async Async.Task Search_SpecificPool_ByState_NotFound_ReturnsEmptyResult() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new PoolSearch(State: new List<PoolState> { PoolState.Init });
|
var req = new PoolSearch(State: new List<PoolState> { PoolState.Init });
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
var func = new PoolFunction(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -127,9 +103,7 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
||||||
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new PoolFunction(Context);
|
||||||
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", new PoolSearch()));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", new PoolSearch()));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -144,15 +118,9 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
||||||
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
var func = new PoolFunction(Context);
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new PoolStop(Name: _poolName, Now: false);
|
var req = new PoolStop(Name: _poolName, Now: false);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("DELETE", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var pool = await Context.PoolOperations.GetByName(_poolName);
|
var pool = await Context.PoolOperations.GetByName(_poolName);
|
||||||
@ -166,15 +134,9 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
||||||
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Halt, null));
|
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Halt, null));
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
var func = new PoolFunction(Context);
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new PoolStop(Name: _poolName, Now: false);
|
var req = new PoolStop(Name: _poolName, Now: false);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("DELETE", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var pool = await Context.PoolOperations.GetByName(_poolName);
|
var pool = await Context.PoolOperations.GetByName(_poolName);
|
||||||
@ -188,15 +150,9 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
||||||
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
var func = new PoolFunction(Context);
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new PoolStop(Name: _poolName, Now: true);
|
var req = new PoolStop(Name: _poolName, Now: true);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("DELETE", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("DELETE", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
var pool = await Context.PoolOperations.GetByName(_poolName);
|
var pool = await Context.PoolOperations.GetByName(_poolName);
|
||||||
@ -209,18 +165,12 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }); // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }); // needed for admin check
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
// need to override instance id
|
// need to override instance id
|
||||||
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
Context.Containers = new TestContainers(Logger, Context.Storage, Context.ServiceConfiguration);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new PoolFunction(Context);
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new PoolCreate(Name: _poolName, Os.Linux, Architecture.x86_64, true);
|
var req = new PoolCreate(Name: _poolName, Os.Linux, Architecture.x86_64, true);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("POST", req));
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
// should get a pool back
|
// should get a pool back
|
||||||
@ -240,15 +190,9 @@ public abstract class PoolTestBase : FunctionTestBase {
|
|||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } }, // needed for admin check
|
||||||
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
new Pool(_poolName, _poolId, Os.Linux, true, Architecture.x86_64, PoolState.Running, null));
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
var func = new PoolFunction(Context);
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: _userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
var func = new PoolFunction(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new PoolCreate(Name: _poolName, Os.Linux, Architecture.x86_64, true);
|
var req = new PoolCreate(Name: _poolName, Os.Linux, Architecture.x86_64, true);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("POST", req));
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
|
||||||
// should get an error back
|
// should get an error back
|
||||||
|
@ -28,27 +28,12 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
public ReproVmssTestBase(ITestOutputHelper output, IStorage storage)
|
public ReproVmssTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("POST", RequestType.Agent)]
|
|
||||||
[InlineData("POST", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("GET", RequestType.Agent)]
|
|
||||||
[InlineData("GET", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("DELETE", RequestType.Agent)]
|
|
||||||
[InlineData("DELETE", RequestType.NoAuthorization)]
|
|
||||||
public async Async.Task UserAuthorization_IsRequired(string method, RequestType authType) {
|
|
||||||
var auth = new TestEndpointAuthorization(authType, Logger, Context);
|
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty(method));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task GetMissingVmFails() {
|
public async Async.Task GetMissingVmFails() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproGet(VmId: Guid.NewGuid());
|
var req = new ReproGet(VmId: Guid.NewGuid());
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
// TODO: should this be 404?
|
// TODO: should this be 404?
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
@ -57,10 +42,10 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task GetAvailableVMsCanReturnEmpty() {
|
public async Async.Task GetAvailableVMsCanReturnEmpty() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproGet(VmId: null); // this means "all available"
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
Assert.Empty(BodyAs<Repro[]>(result));
|
Assert.Empty(BodyAs<Repro[]>(result));
|
||||||
}
|
}
|
||||||
@ -77,10 +62,10 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
Auth: new SecretValue<Authentication>(new Authentication("", "", "")),
|
Auth: new SecretValue<Authentication>(new Authentication("", "", "")),
|
||||||
Os: Os.Linux));
|
Os: Os.Linux));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproGet(VmId: null); // this means "all available"
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
var repro = Assert.Single(BodyAs<Repro[]>(result));
|
var repro = Assert.Single(BodyAs<Repro[]>(result));
|
||||||
Assert.Equal(vmId, repro.VmId);
|
Assert.Equal(vmId, repro.VmId);
|
||||||
@ -101,10 +86,10 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
Auth: new SecretAddress<Authentication>(secretUri),
|
Auth: new SecretAddress<Authentication>(secretUri),
|
||||||
Os: Os.Linux));
|
Os: Os.Linux));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproGet(VmId: vmId);
|
var req = new ReproGet(VmId: vmId);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
Assert.Equal(vmId, BodyAs<Repro>(result).VmId);
|
Assert.Equal(vmId, BodyAs<Repro>(result).VmId);
|
||||||
}
|
}
|
||||||
@ -128,37 +113,23 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
Os: Os.Linux,
|
Os: Os.Linux,
|
||||||
State: VmState.Stopped));
|
State: VmState.Stopped));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproGet(VmId: null); // this means "all available"
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
Assert.Empty(BodyAs<Repro[]>(result));
|
Assert.Empty(BodyAs<Repro[]>(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Async.Task CannotCreateVMWithoutCredentials() {
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
|
||||||
var req = new ReproCreate(Container.Parse("abcd"), "/", 12345);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
|
||||||
Assert.Equal(new ProblemDetails(400, "INVALID_REQUEST", "unable to find authorization token"), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CannotCreateVMForMissingReport() {
|
public async Async.Task CannotCreateVMForMissingReport() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// setup fake user
|
// setup fake user
|
||||||
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
var ctx = new TestFunctionContext();
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
ctx.SetUserAuthInfo(new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn"));
|
||||||
|
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var req = new ReproCreate(Container.Parse("abcd"), "/", 12345);
|
var req = new ReproCreate(Container.Parse("abcd"), "/", 12345);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
Assert.Equal(new ProblemDetails(400, "UNABLE_TO_FIND", "unable to find report"), err);
|
Assert.Equal(new ProblemDetails(400, "UNABLE_TO_FIND", "unable to find report"), err);
|
||||||
@ -209,15 +180,13 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
public async Async.Task CannotCreateVMForMissingTask() {
|
public async Async.Task CannotCreateVMForMissingTask() {
|
||||||
var (container, filename) = await CreateContainerWithReport(Guid.NewGuid(), Guid.NewGuid());
|
var (container, filename) = await CreateContainerWithReport(Guid.NewGuid(), Guid.NewGuid());
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// setup fake user
|
// setup fake user
|
||||||
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
var ctx = new TestFunctionContext();
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
ctx.SetUserAuthInfo(new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn"));
|
||||||
|
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var req = new ReproCreate(container, filename, 12345);
|
var req = new ReproCreate(container, filename, 12345);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
Assert.Equal(new ProblemDetails(400, "INVALID_REQUEST", "unable to find task"), err);
|
Assert.Equal(new ProblemDetails(400, "INVALID_REQUEST", "unable to find task"), err);
|
||||||
@ -241,15 +210,13 @@ public abstract class ReproVmssTestBase : FunctionTestBase {
|
|||||||
null,
|
null,
|
||||||
new TaskDetails(TaskType.LibfuzzerFuzz, 12345))));
|
new TaskDetails(TaskType.LibfuzzerFuzz, 12345))));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// setup fake user
|
// setup fake user
|
||||||
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
var ctx = new TestFunctionContext();
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
ctx.SetUserAuthInfo(new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn"));
|
||||||
|
|
||||||
var func = new ReproVmss(Logger, auth, Context);
|
var func = new ReproVmss(Logger, Context);
|
||||||
var req = new ReproCreate(container, filename, 12345);
|
var req = new ReproCreate(container, filename, 12345);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
var repro = BodyAs<Repro>(result);
|
var repro = BodyAs<Repro>(result);
|
||||||
Assert.Equal(taskId, repro.TaskId);
|
Assert.Equal(taskId, repro.TaskId);
|
||||||
|
@ -25,28 +25,10 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
public ScalesetTestBase(ITestOutputHelper output, IStorage storage)
|
public ScalesetTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) { }
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("POST", RequestType.Agent)]
|
|
||||||
[InlineData("POST", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("PATCH", RequestType.Agent)]
|
|
||||||
[InlineData("PATCH", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("GET", RequestType.Agent)]
|
|
||||||
[InlineData("GET", RequestType.NoAuthorization)]
|
|
||||||
[InlineData("DELETE", RequestType.Agent)]
|
|
||||||
[InlineData("DELETE", RequestType.NoAuthorization)]
|
|
||||||
public async Async.Task UserAuthorization_IsRequired(string method, RequestType authType) {
|
|
||||||
var auth = new TestEndpointAuthorization(authType, Logger, Context);
|
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.Empty(method));
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_SpecificScaleset_ReturnsErrorIfNoneFound() {
|
public async Async.Task Search_SpecificScaleset_ReturnsErrorIfNoneFound() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new ScalesetSearch(ScalesetId: ScalesetId.Parse(Guid.NewGuid().ToString()));
|
var req = new ScalesetSearch(ScalesetId: ScalesetId.Parse(Guid.NewGuid().ToString()));
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
@ -56,10 +38,8 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Search_AllScalesets_ReturnsEmptyIfNoneFound() {
|
public async Async.Task Search_AllScalesets_ReturnsEmptyIfNoneFound() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new ScalesetSearch();
|
var req = new ScalesetSearch();
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
@ -81,10 +61,8 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
new Node(poolName, Guid.NewGuid(), poolId, "version", ScalesetId: scalesetId)
|
new Node(poolName, Guid.NewGuid(), poolId, "version", ScalesetId: scalesetId)
|
||||||
);
|
);
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new ScalesetSearch(ScalesetId: scalesetId);
|
var req = new ScalesetSearch(ScalesetId: scalesetId);
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
@ -95,17 +73,10 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Create_Scaleset() {
|
public async Async.Task Create_Scaleset() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userObjectId = Guid.NewGuid();
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
var poolName = PoolName.Parse("mypool");
|
var poolName = PoolName.Parse("mypool");
|
||||||
await Context.InsertAll(
|
await Context.InsertAll(
|
||||||
// user must be admin
|
// config must exist
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { userObjectId } },
|
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!),
|
||||||
// pool must exist and be managed
|
// pool must exist and be managed
|
||||||
new Pool(poolName, Guid.NewGuid(), Os.Linux, Managed: true, Architecture.x86_64, PoolState.Running));
|
new Pool(poolName, Guid.NewGuid(), Os.Linux, Managed: true, Architecture.x86_64, PoolState.Running));
|
||||||
|
|
||||||
@ -118,8 +89,8 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
SpotInstances: false,
|
SpotInstances: false,
|
||||||
Tags: new Dictionary<string, string>());
|
Tags: new Dictionary<string, string>());
|
||||||
|
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
|
||||||
@ -132,17 +103,6 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Create_Scaleset_Under_NonExistent_Pool_Provides_Error() {
|
public async Async.Task Create_Scaleset_Under_NonExistent_Pool_Provides_Error() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
// override the found user credentials
|
|
||||||
var userObjectId = Guid.NewGuid();
|
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: userObjectId, "upn");
|
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
|
||||||
|
|
||||||
await Context.InsertAll(
|
|
||||||
// user must be admin
|
|
||||||
new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { userObjectId } });
|
|
||||||
|
|
||||||
var poolName = PoolName.Parse("nosuchpool");
|
var poolName = PoolName.Parse("nosuchpool");
|
||||||
// pool not created
|
// pool not created
|
||||||
var req = new ScalesetCreate(
|
var req = new ScalesetCreate(
|
||||||
@ -154,8 +114,8 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
SpotInstances: false,
|
SpotInstances: false,
|
||||||
Tags: new Dictionary<string, string>());
|
Tags: new Dictionary<string, string>());
|
||||||
|
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Admin(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
|
||||||
|
@ -30,8 +30,7 @@ public abstract class TasksTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task SpecifyingVmIsNotPermitted() {
|
public async Async.Task SpecifyingVmIsNotPermitted() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Tasks(Context);
|
||||||
var func = new Tasks(Logger, auth, Context);
|
|
||||||
|
|
||||||
var req = new TaskCreate(
|
var req = new TaskCreate(
|
||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
@ -43,7 +42,8 @@ public abstract class TasksTestBase : FunctionTestBase {
|
|||||||
var serialized = (JsonObject?)JsonSerializer.SerializeToNode(req, EntityConverter.GetJsonSerializerOptions());
|
var serialized = (JsonObject?)JsonSerializer.SerializeToNode(req, EntityConverter.GetJsonSerializerOptions());
|
||||||
serialized!["vm"] = new JsonObject { { "fake", 1 } };
|
serialized!["vm"] = new JsonObject { { "fake", 1 } };
|
||||||
var testData = new TestHttpRequestData("POST", new BinaryData(JsonSerializer.SerializeToUtf8Bytes(serialized, EntityConverter.GetJsonSerializerOptions())));
|
var testData = new TestHttpRequestData("POST", new BinaryData(JsonSerializer.SerializeToUtf8Bytes(serialized, EntityConverter.GetJsonSerializerOptions())));
|
||||||
var result = await func.Run(testData);
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(testData, ctx);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
Assert.Equal("Unexpected property: \"vm\"", err.Detail);
|
Assert.Equal("Unexpected property: \"vm\"", err.Detail);
|
||||||
@ -51,12 +51,11 @@ public abstract class TasksTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task PoolIsRequired() {
|
public async Async.Task PoolIsRequired() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Tasks(Context);
|
||||||
var func = new Tasks(Logger, auth, Context);
|
|
||||||
|
|
||||||
// override the found user credentials - need these to check for admin
|
// override the found user credentials - need these to store user
|
||||||
var userInfo = new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: Guid.NewGuid(), "upn");
|
var ctx = new TestFunctionContext();
|
||||||
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult<UserInfo>.Ok(userInfo));
|
ctx.SetUserAuthInfo(new UserInfo(ApplicationId: Guid.NewGuid(), ObjectId: Guid.NewGuid(), "upn"));
|
||||||
|
|
||||||
var req = new TaskCreate(
|
var req = new TaskCreate(
|
||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
@ -64,7 +63,7 @@ public abstract class TasksTestBase : FunctionTestBase {
|
|||||||
new TaskDetails(TaskType.DotnetCoverage, 100),
|
new TaskDetails(TaskType.DotnetCoverage, 100),
|
||||||
null! /* <- here */);
|
null! /* <- here */);
|
||||||
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
var err = BodyAs<ProblemDetails>(result);
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
Assert.Equal("The Pool field is required.", err.Detail);
|
Assert.Equal("The Pool field is required.", err.Detail);
|
||||||
@ -72,15 +71,14 @@ public abstract class TasksTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CanSearchWithJobIdAndEmptyListOfStates() {
|
public async Async.Task CanSearchWithJobIdAndEmptyListOfStates() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
|
||||||
|
|
||||||
var req = new TaskSearch(
|
var req = new TaskSearch(
|
||||||
JobId: Guid.NewGuid(),
|
JobId: Guid.NewGuid(),
|
||||||
TaskId: null,
|
TaskId: null,
|
||||||
State: new List<TaskState>());
|
State: new List<TaskState>());
|
||||||
|
|
||||||
var func = new Tasks(Logger, auth, Context);
|
var func = new Tasks(Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var ctx = new TestFunctionContext();
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req), ctx);
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,7 @@ public abstract class ToolsTestBase : FunctionTestBase {
|
|||||||
var r = await toolsContainerClient.UploadBlobAsync(path.ToString(), BinaryData.FromString(content.ToString()));
|
var r = await toolsContainerClient.UploadBlobAsync(path.ToString(), BinaryData.FromString(content.ToString()));
|
||||||
Assert.False(r.GetRawResponse().IsError);
|
Assert.False(r.GetRawResponse().IsError);
|
||||||
}
|
}
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var func = new Tools(Context);
|
||||||
var func = new Tools(auth, Context);
|
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", ""));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", ""));
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
79
src/ApiService/Tests/AuthTests.cs
Normal file
79
src/ApiService/Tests/AuthTests.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Castle.Core.Internal;
|
||||||
|
using Microsoft.Azure.Functions.Worker;
|
||||||
|
using Microsoft.OneFuzz.Service.Auth;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
public class AuthTests {
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> AllFunctionEntryPoints() {
|
||||||
|
var asm = typeof(AuthorizeAttribute).Assembly;
|
||||||
|
foreach (var type in asm.GetTypes()) {
|
||||||
|
if (type.Namespace == "ApiService.TestHooks"
|
||||||
|
|| type.Name == "TestHooks") {
|
||||||
|
// skip test hooks
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var method in type.GetMethods()) {
|
||||||
|
if (method.GetCustomAttribute<FunctionAttribute>() is not null) {
|
||||||
|
// it's a function entrypoint
|
||||||
|
yield return new object[] { type, method };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(AllFunctionEntryPoints))]
|
||||||
|
public void AllFunctionsHaveAuthAttributes(Type type, MethodInfo methodInfo) {
|
||||||
|
var trigger = methodInfo.GetParameters().First().GetCustomAttribute<HttpTriggerAttribute>();
|
||||||
|
if (trigger is null) {
|
||||||
|
return; // not an HTTP function
|
||||||
|
}
|
||||||
|
|
||||||
|
// built-in auth level should be anonymous - we are implementing our own authorization
|
||||||
|
Assert.Equal(AuthorizationLevel.Anonymous, trigger.AuthLevel);
|
||||||
|
|
||||||
|
if (type.Name == "Config" && methodInfo.Name == "Run") {
|
||||||
|
// this method alone is allowed to be anonymous
|
||||||
|
Assert.Null(methodInfo.GetAttribute<AuthorizeAttribute>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// authorize attribute can be on class or method
|
||||||
|
var authAttribute = methodInfo.GetAttribute<AuthorizeAttribute>()
|
||||||
|
?? type.GetAttribute<AuthorizeAttribute>();
|
||||||
|
Assert.NotNull(authAttribute);
|
||||||
|
|
||||||
|
// naming convention: check that Agent* functions have Allow.Agent, and none other
|
||||||
|
var functionAttribute = methodInfo.GetCustomAttribute<FunctionAttribute>()!;
|
||||||
|
if (functionAttribute.Name.StartsWith("Agent")) {
|
||||||
|
Assert.Equal(Allow.Agent, authAttribute.Allow);
|
||||||
|
} else {
|
||||||
|
Assert.NotEqual(Allow.Agent, authAttribute.Allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// naming convention: all *_Admin functions should be ALlow.Admin
|
||||||
|
// (some that aren't _Admin also require it)
|
||||||
|
if (functionAttribute.Name.EndsWith("_Admin")) {
|
||||||
|
Assert.Equal(Allow.Admin, authAttribute.Allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure other methods that _aren't_ function entry points don't have it,
|
||||||
|
// because it won't do anything there, and having it present would be misleading
|
||||||
|
foreach (var otherMethod in type.GetMethods()) {
|
||||||
|
if (otherMethod.GetCustomAttribute<FunctionAttribute>() is null) {
|
||||||
|
Assert.True(
|
||||||
|
otherMethod.GetCustomAttribute<AuthorizeAttribute>() is null,
|
||||||
|
"non-[Function] methods must not have [Authorize]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user