public async Task InvokeAsync(HttpContext context) { if (!context.Request.Path.StartsWithSegments("/event/live", StringComparison.OrdinalIgnoreCase)) { await this.next(context).ConfigureAwait(false); return; } if (!context.WebSockets.IsWebSocketRequest) { context.Response.StatusCode = 400; return; } if (!context.Request.Query.ContainsKey("tk")) { context.Response.StatusCode = 401; context.Response.Body = Stream.Null; return; } var values = context.Request.Query["tk"]; if (values.Count == 0) { context.Response.StatusCode = 401; context.Response.Body = Stream.Null; return; } var authToken = values[0]; var session = await employeeSessions.FetchEmployeeSessionAndEmployeeFromToken(authToken) .Ensure(s => s.HasValue, "Employee was found") .OnSuccess(s => s.Value) .ConfigureAwait(false); if (session.IsFailure) { context.Response.StatusCode = 401; context.Response.Body = Stream.Null; } WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); using var consumer = new DelegatingOrderEventConsumer(settings, constants, loggerFactory, session.Value.VenueId); consumer.OnNewEvent += async(_, data) => { logger.LogDebug("Received order update message for web consumers. Transmitting to connected sockets."); await Broadcast(webSocket, Encoding.UTF8.GetBytes(data)).ConfigureAwait(false); }; consumer.StartConsuming(); await Receive(webSocket, async (_, buffer) => { WebsocketStatusMessage message = null; try { var rawMessage = Encoding.UTF8.GetString(buffer); message = JsonConvert.DeserializeObject <WebsocketStatusMessage>(rawMessage); } catch (Exception) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Bad Request", CancellationToken.None).ConfigureAwait(false); return; } if (message == null) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Bad Request", CancellationToken.None).ConfigureAwait(false); return; } var currentSession = await employeeSessions.FetchEmployeeSessionAndEmployeeFromToken(message.AuthToken) .Ensure(s => s.HasValue, "Employee was found") .OnSuccess(s => s.Value) .ConfigureAwait(false); if (currentSession.IsFailure) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Unauthorised", CancellationToken.None).ConfigureAwait(false); } if (currentSession.Value.VenueId != session.Value.VenueId) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Unauthorised", CancellationToken.None).ConfigureAwait(false); } }).ConfigureAwait(false); await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); }
private async Task<AuthorisationResult> EnforceRBACRequirements(AuthContext authContext, Microsoft.AspNetCore.Routing.RouteData routeData) { var userType = authContext.UserType; // Grab the route name via the [ActionName(...)] annotation on a controller method. // If you don't annotate your methods, or have not configured this route as authenticated, we won't apply any auth restrictions. var routeName = (string)routeData.Values["action"]; if (routeName == null || !settings.Meta.AuthRequirements.ContainsKey(routeName)) { return AuthorisationResult.InjectAndContinue; } var requirements = settings.Meta.AuthRequirements[routeName]; Maybe<Employee> employee = null; Maybe<EmployeeRole> employeeRole = null; Maybe<User> user = null; if (userType != settings.Meta.AuthRequirements[routeName].UserType && UserType.Any != settings.Meta.AuthRequirements[routeName].UserType) { return AuthorisationResult.AbortUnauthorised; } if (authContext.AuthToken.HasNoValue) { return AuthorisationResult.AbortUnauthorised; } var authToken = authContext.AuthToken.Value; // Depending on the session type, attempt to fetch the equivalent model (User or Employee). if (userType == UserType.Employee) { employee = (await employeeSessions.FetchEmployeeSessionAndEmployeeFromToken(authToken).ConfigureAwait(false)) .OnBoth(x => x.IsSuccess ? x.Value : Maybe<Employee>.None ); if (employee.HasNoValue) { return AuthorisationResult.AbortUnauthorised; } // For employees, their permissions are scoped to an employee role. First we fetch the role. employeeRole = (await employeeRoles.FetchEmployeeRole(employee.Value.RoleId).ConfigureAwait(false)) .OnBoth(x => x.IsSuccess ? x.Value : Maybe<EmployeeRole>.None ); // If the employee role wasn't found but the route didn't require any permissions, we let them through. if (employeeRole.HasNoValue && RouteRequiresAtLeastOnePermission(requirements)) { return AuthorisationResult.AbortUnauthorised; } // Verify each of the role permissions in turn, if one is missing and the route requires it, deny access. if (!RoleHasCorrectPermissions(employeeRole.Value, requirements)) { return AuthorisationResult.AbortUnauthorised; } } else if (userType == UserType.User) { user = (await userSessions.FetchUserSessionAndUserFromToken(authToken, (int)authContext.AuthType).ConfigureAwait(false)) .OnBoth(x => x.IsSuccess ? x.Value : Maybe<User>.None ); if (user.HasNoValue) { return AuthorisationResult.AbortUnauthorised; } } // After verifying the user's permissions, update the context with relevant user data and continue. authContext.Employee = employee; authContext.EmployeeRole = employeeRole; authContext.User = user; return AuthorisationResult.Continue; }