Exemple #1
0
        public static Dictionary <string, RouteInformation> Scan()
        {
            var routes = new Dictionary <string, RouteInformation>(StringComparer.OrdinalIgnoreCase);

            var actions = typeof(RouteScanner).GetTypeInfo().Assembly.GetTypes()
                          .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                          .Where(type => type.IsDefined(typeof(RavenActionAttribute)))
                          .ToList();

            foreach (var memberInfo in actions)
            {
                foreach (var route in memberInfo.GetCustomAttributes <RavenActionAttribute>())
                {
                    RouteInformation routeInfo;
                    var routeKey = route.Method + route.Path;
                    if (routes.TryGetValue(routeKey, out routeInfo) == false)
                    {
                        routes[routeKey] = routeInfo = new RouteInformation(route.Method, route.Path, route.NoAuthorizationRequired);
                    }
                    else
                    {
                        throw new InvalidOperationException($"A duplicate route found: {routeKey} on {memberInfo.DeclaringType}.{memberInfo.Name}");
                    }
                    routeInfo.Build(memberInfo);
                }
            }

            return(routes);
        }
Exemple #2
0
 private static void ThrowUnknownAuthStatus(RouteInformation route)
 {
     throw new ArgumentOutOfRangeException("Unknown route auth status: " + route.AuthorizationStatus);
 }
Exemple #3
0
        private bool TryAuthorize(RouteInformation route, HttpContext context, DocumentDatabase database)
        {
            var feature = context.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection;

            if (feature.WrittenToAuditLog == 0) // intentionally racy, we'll check it again later
            {
                var auditLog = LoggingSource.AuditLog.IsInfoEnabled ? LoggingSource.AuditLog.GetLogger("RequestRouter", "Audit") : null;

                if (auditLog != null)
                {
                    // only one thread will win it, technically, there can't really be threading
                    // here, because there is a single connection, but better to be safe
                    if (Interlocked.CompareExchange(ref feature.WrittenToAuditLog, 1, 0) == 0)
                    {
                        if (feature.WrongProtocolMessage != null)
                        {
                            auditLog.Info($"Connection from {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} " +
                                          $"used the wrong protocol and will be rejected. {feature.WrongProtocolMessage}");
                        }
                        else
                        {
                            auditLog.Info($"Connection from {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} " +
                                          $"with certificate '{feature.Certificate?.Subject} ({feature.Certificate?.Thumbprint})', status: {feature.StatusForAudit}, " +
                                          $"databases: [{string.Join(", ", feature.AuthorizedDatabases.Keys)}]");

                            var conLifetime = context.Features.Get <IConnectionLifetimeFeature>();
                            if (conLifetime != null)
                            {
                                var msg = $"Connection {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} closed. Was used with: " +
                                          $"with certificate '{feature.Certificate?.Subject} ({feature.Certificate?.Thumbprint})', status: {feature.StatusForAudit}, " +
                                          $"databases: [{string.Join(", ", feature.AuthorizedDatabases.Keys)}]";

                                conLifetime.ConnectionClosed.Register(() =>
                                {
                                    auditLog.Info(msg);
                                });
                            }
                        }
                    }
                }
            }

            var authenticationStatus = feature?.Status;

            switch (route.AuthorizationStatus)
            {
            case AuthorizationStatus.UnauthenticatedClients:
                var userWantsToAccessStudioMainPage = context.Request.Path == "/studio/index.html";
                if (userWantsToAccessStudioMainPage)
                {
                    switch (authenticationStatus)
                    {
                    case null:
                    case RavenServer.AuthenticationStatus.NoCertificateProvided:
                    case RavenServer.AuthenticationStatus.Expired:
                    case RavenServer.AuthenticationStatus.NotYetValid:
                    case RavenServer.AuthenticationStatus.None:
                    case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                        UnlikelyFailAuthorization(context, database?.Name, feature, route.AuthorizationStatus);
                        return(false);
                    }
                }

                return(true);

            case AuthorizationStatus.ClusterAdmin:
            case AuthorizationStatus.Operator:
            case AuthorizationStatus.ValidUser:
            case AuthorizationStatus.DatabaseAdmin:

                switch (authenticationStatus)
                {
                case null:
                case RavenServer.AuthenticationStatus.NoCertificateProvided:
                case RavenServer.AuthenticationStatus.Expired:
                case RavenServer.AuthenticationStatus.NotYetValid:
                case RavenServer.AuthenticationStatus.None:
                case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                    UnlikelyFailAuthorization(context, database?.Name, feature, route.AuthorizationStatus);
                    return(false);

                case RavenServer.AuthenticationStatus.Allowed:
                    if (route.AuthorizationStatus == AuthorizationStatus.Operator || route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }

                    if (database == null)
                    {
                        return(true);
                    }
                    if (feature.CanAccess(database.Name, route.AuthorizationStatus == AuthorizationStatus.DatabaseAdmin))
                    {
                        return(true);
                    }

                    goto case RavenServer.AuthenticationStatus.None;

                case RavenServer.AuthenticationStatus.Operator:
                    if (route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }
                    return(true);

                case RavenServer.AuthenticationStatus.ClusterAdmin:
                    return(true);

                default:
                    throw new ArgumentOutOfRangeException();
                }

            default:
                ThrowUnknownAuthStatus(route);
                return(false);    // never hit
            }
        }
Exemple #4
0
        private bool TryAuthorize(RouteInformation route, HttpContext context, DocumentDatabase database)
        {
            var feature = context.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection;

            var authenticationStatus = feature?.Status;

            switch (route.AuthorizationStatus)
            {
            case AuthorizationStatus.UnauthenticatedClients:
                var userWantsToAccessStudioMainPage = context.Request.Path == "/studio/index.html";
                if (userWantsToAccessStudioMainPage)
                {
                    switch (authenticationStatus)
                    {
                    case null:
                    case RavenServer.AuthenticationStatus.NoCertificateProvided:
                    case RavenServer.AuthenticationStatus.Expired:
                    case RavenServer.AuthenticationStatus.NotYetValid:
                    case RavenServer.AuthenticationStatus.None:
                    case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                        UnlikelyFailAuthorization(context, database?.Name, feature);
                        return(false);
                    }
                }

                return(true);

            case AuthorizationStatus.ClusterAdmin:
            case AuthorizationStatus.Operator:
            case AuthorizationStatus.ValidUser:
            case AuthorizationStatus.DatabaseAdmin:

                switch (authenticationStatus)
                {
                case null:
                case RavenServer.AuthenticationStatus.NoCertificateProvided:
                case RavenServer.AuthenticationStatus.Expired:
                case RavenServer.AuthenticationStatus.NotYetValid:
                case RavenServer.AuthenticationStatus.None:
                case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                    UnlikelyFailAuthorization(context, database?.Name, feature);
                    return(false);

                case RavenServer.AuthenticationStatus.Allowed:
                    if (route.AuthorizationStatus == AuthorizationStatus.Operator || route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }

                    if (database == null)
                    {
                        return(true);
                    }

                    if (feature.CanAccess(database.Name, route.AuthorizationStatus == AuthorizationStatus.DatabaseAdmin))
                    {
                        return(true);
                    }

                    goto case RavenServer.AuthenticationStatus.None;

                case RavenServer.AuthenticationStatus.Operator:
                    if (route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }
                    return(true);

                case RavenServer.AuthenticationStatus.ClusterAdmin:
                    return(true);

                default:
                    throw new ArgumentOutOfRangeException();
                }

            default:
                ThrowUnknownAuthStatus(route);
                return(false);    // never hit
            }
        }
Exemple #5
0
        public static Dictionary <string, RouteInformation> Scan(Func <RavenActionAttribute, bool> predicate = null)
        {
            var routes = new Dictionary <string, RouteInformation>(StringComparer.OrdinalIgnoreCase);

            var corsHandler = typeof(CorsPreflightHandler).GetMethod(nameof(CorsPreflightHandler.HandlePreflightRequest));

            var actions = typeof(RouteScanner).Assembly.GetTypes()
                          .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                          .Where(type => type.IsDefined(typeof(RavenActionAttribute)))
                          .ToList();

            foreach (var memberInfo in actions)
            {
                foreach (var route in memberInfo.GetCustomAttributes <RavenActionAttribute>())
                {
                    if (predicate != null && predicate(route) == false)
                    {
                        continue;
                    }

                    if (route.CorsMode != CorsMode.None)
                    {
                        // register endpoint for preflight request
                        var optionsRouteKey = "OPTIONS" + route.Path;

                        // we don't check for duplicates here, as single endpoint like: /admin/cluster/node might have 2 verbs (PUT, DELETE),
                        // but we need single OPTIONS handler

                        if (routes.TryGetValue(optionsRouteKey, out RouteInformation optionsRouteInfo) == false)
                        {
                            routes[optionsRouteKey] = optionsRouteInfo = new RouteInformation(
                                "OPTIONS",
                                route.Path,
                                route.RequiredAuthorization,
                                route.EndpointType,
                                route.SkipUsagesCount,
                                route.SkipLastRequestTimeUpdate,
                                route.CorsMode,
                                route.IsDebugInformationEndpoint,
                                route.DisableOnCpuCreditsExhaustion);

                            optionsRouteInfo.Build(corsHandler);
                        }
                    }

                    var routeKey = route.Method + route.Path;
                    if (routes.TryGetValue(routeKey, out RouteInformation routeInfo) == false)
                    {
                        routes[routeKey] = routeInfo = new RouteInformation(
                            route.Method,
                            route.Path,
                            route.RequiredAuthorization,
                            route.EndpointType,
                            route.SkipUsagesCount,
                            route.SkipLastRequestTimeUpdate,
                            route.CorsMode,
                            route.IsDebugInformationEndpoint,
                            route.DisableOnCpuCreditsExhaustion);
                    }
                    else
                    {
                        throw new InvalidOperationException($"A duplicate route found: {routeKey} on {memberInfo.DeclaringType}.{memberInfo.Name}");
                    }
                    routeInfo.Build(memberInfo);
                }
            }

            return(routes);
        }
Exemple #6
0
        internal bool CanAccessRoute(RouteInformation route, HttpContext context, string databaseName, RavenServer.AuthenticateConnection feature, out RavenServer.AuthenticationStatus authenticationStatus)
        {
            authenticationStatus = feature?.Status ?? RavenServer.AuthenticationStatus.None;
            switch (route.AuthorizationStatus)
            {
            case AuthorizationStatus.UnauthenticatedClients:
                var userWantsToAccessStudioMainPage = context.Request.Path == "/studio/index.html";
                if (userWantsToAccessStudioMainPage)
                {
                    switch (authenticationStatus)
                    {
                    case RavenServer.AuthenticationStatus.NoCertificateProvided:
                    case RavenServer.AuthenticationStatus.Expired:
                    case RavenServer.AuthenticationStatus.NotYetValid:
                    case RavenServer.AuthenticationStatus.None:
                    case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                    case RavenServer.AuthenticationStatus.UnfamiliarIssuer:
                        return(false);
                    }
                }

                return(true);

            case AuthorizationStatus.ClusterAdmin:
            case AuthorizationStatus.Operator:
            case AuthorizationStatus.ValidUser:
            case AuthorizationStatus.DatabaseAdmin:
            case AuthorizationStatus.RestrictedAccess:
                switch (authenticationStatus)
                {
                case RavenServer.AuthenticationStatus.NoCertificateProvided:
                case RavenServer.AuthenticationStatus.Expired:
                case RavenServer.AuthenticationStatus.NotYetValid:
                case RavenServer.AuthenticationStatus.None:
                    return(false);

                case RavenServer.AuthenticationStatus.UnfamiliarCertificate:
                case RavenServer.AuthenticationStatus.UnfamiliarIssuer:
                    // we allow an access to the restricted endpoints with an unfamiliar certificate, since we will authorize it at the endpoint level
                    if (route.AuthorizationStatus == AuthorizationStatus.RestrictedAccess)
                    {
                        return(true);
                    }
                    ;
                    goto case RavenServer.AuthenticationStatus.None;

                case RavenServer.AuthenticationStatus.Allowed:
                    if (route.AuthorizationStatus == AuthorizationStatus.Operator || route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }

                    if (databaseName == null)
                    {
                        return(true);
                    }
                    if (feature.CanAccess(databaseName, route.AuthorizationStatus == AuthorizationStatus.DatabaseAdmin, route.EndpointType == EndpointType.Write))
                    {
                        return(true);
                    }

                    goto case RavenServer.AuthenticationStatus.None;

                case RavenServer.AuthenticationStatus.Operator:
                    if (route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin)
                    {
                        goto case RavenServer.AuthenticationStatus.None;
                    }
                    return(true);

                case RavenServer.AuthenticationStatus.ClusterAdmin:
                    return(true);

                default:
                    throw new ArgumentOutOfRangeException();
                }

            default:
                ThrowUnknownAuthStatus(route);
                return(false);    // never hit
            }
        }
Exemple #7
0
        internal async ValueTask <(bool Authorized, RavenServer.AuthenticationStatus Status)> TryAuthorizeAsync(RouteInformation route, HttpContext context, DocumentDatabase database)
        {
            var feature = context.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection;

            if (feature.WrittenToAuditLog == 0) // intentionally racy, we'll check it again later
            {
                var auditLog = LoggingSource.AuditLog.IsInfoEnabled ? LoggingSource.AuditLog.GetLogger("RequestRouter", "Audit") : null;

                if (auditLog != null)
                {
                    // only one thread will win it, technically, there can't really be threading
                    // here, because there is a single connection, but better to be safe
                    if (Interlocked.CompareExchange(ref feature.WrittenToAuditLog, 1, 0) == 0)
                    {
                        if (feature.WrongProtocolMessage != null)
                        {
                            auditLog.Info($"Connection from {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} " +
                                          $"used the wrong protocol and will be rejected. {feature.WrongProtocolMessage}");
                        }
                        else
                        {
                            auditLog.Info($"Connection from {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} " +
                                          $"with certificate '{feature.Certificate?.Subject} ({feature.Certificate?.Thumbprint})', status: {feature.StatusForAudit}, " +
                                          $"databases: [{string.Join(", ", feature.AuthorizedDatabases.Keys)}]");

                            var conLifetime = context.Features.Get <IConnectionLifetimeFeature>();
                            if (conLifetime != null)
                            {
                                var msg = $"Connection {context.Connection.RemoteIpAddress}:{context.Connection.RemotePort} closed. Was used with: " +
                                          $"with certificate '{feature.Certificate?.Subject} ({feature.Certificate?.Thumbprint})', status: {feature.StatusForAudit}, " +
                                          $"databases: [{string.Join(", ", feature.AuthorizedDatabases.Keys)}]";

                                CancellationTokenRegistration cancellationTokenRegistration = default;

                                cancellationTokenRegistration = conLifetime.ConnectionClosed.Register(() =>
                                {
                                    auditLog.Info(msg);
                                    cancellationTokenRegistration.Dispose();
                                });
                            }
                        }
                    }
                }
            }

            if (CanAccessRoute(route, context, database?.Name, feature, out var authenticationStatus) == false)
            {
                await UnlikelyFailAuthorizationAsync(context, database?.Name, feature, route.AuthorizationStatus);

                return(false, authenticationStatus);
            }

            return(true, authenticationStatus);
        }