コード例 #1
0
        /// <inheritdoc />
        public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            if (systemIdentity == null)
            {
                throw new ArgumentNullException(nameof(systemIdentity));
            }
            lock (cachedIdentities)
            {
                var uid = systemIdentity.Uid;
                logger.LogDebug("Caching system identity {0} of user {1}", uid, user.Id);

                if (cachedIdentities.TryGetValue(user.Id.Value, out var identCache))
                {
                    logger.LogTrace("Expiring previously cached identity...");
                    identCache.Dispose();                     // also clears it out
                }

                identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () =>
                {
                    logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id);
                    lock (cachedIdentities)
                        cachedIdentities.Remove(user.Id.Value);
                }, expiry);
                cachedIdentities.Add(user.Id.Value, identCache);
            }
        }
コード例 #2
0
        /// <summary>
        /// Construct an <see cref="IdentityCache"/>
        /// </summary>
        /// <param name="systemIdentity">The value of <see cref="SystemIdentity"/></param>
        /// <param name="onExpiry">The <see cref="Action"/> to take on expiry</param>
        /// <param name="expiry">The <see cref="DateTimeOffset"/></param>
        public IdentityCacheObject(ISystemIdentity systemIdentity, Action onExpiry, DateTimeOffset expiry)
        {
            SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity));
            if (onExpiry == null)
            {
                throw new ArgumentNullException(nameof(onExpiry));
            }
            var now = DateTimeOffset.Now;

            if (expiry < now)
            {
                throw new ArgumentOutOfRangeException(nameof(expiry), expiry, "expiry must be greater than DateTimeOffset.Now!");
            }

            cancellationTokenSource = new CancellationTokenSource();

            async Task DisposeOnExipiry(CancellationToken cancellationToken)
            {
                using (SystemIdentity)
                    try
                    {
                        await Task.Delay(expiry - now, cancellationToken).ConfigureAwait(false);
                    }
                    finally
                    {
                        onExpiry();
                    }
            }

            task = DisposeOnExipiry(cancellationTokenSource.Token);
        }
コード例 #3
0
 /// <summary>
 /// Construct an <see cref="AuthenticationContext"/>
 /// </summary>
 /// <param name="systemIdentity">The value of <see cref="SystemIdentity"/></param>
 /// <param name="user">The value of <see cref="User"/></param>
 /// <param name="instanceUser">The value of <see cref="InstanceUser"/></param>
 public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstanceUser instanceUser)
 {
     this.user = user ?? throw new ArgumentNullException(nameof(user));
     if (systemIdentity == null && User.SystemIdentifier != null)
     {
         throw new ArgumentNullException(nameof(systemIdentity));
     }
     InstanceUser   = instanceUser;
     SystemIdentity = systemIdentity;
 }
コード例 #4
0
 /// <summary>
 /// If a <see cref="ForbidResult"/> should be returned from actions due to conflicts with one or both of the <see cref="Api.Models.Instance.ConfigurationType"/> or the <see cref="IAuthenticationContext.SystemIdentity"/> or a given <paramref name="path"/> tries to access parent directories
 /// </summary>
 /// <param name="path">The path to validate if any</param>
 /// <param name="systemIdentityToUse">The <see cref="ISystemIdentity"/> to use when calling into <see cref="Components.StaticFiles.IConfiguration"/></param>
 /// <returns><see langword="true"/> if a <see cref="ForbidResult"/> should be returned, <see langword="false"/> otherwise</returns>
 bool ForbidDueToModeConflicts(string path, out ISystemIdentity systemIdentityToUse)
 {
     if (Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null) || (path != null && ioManager.PathContainsParentAccess(path)))
     {
         systemIdentityToUse = null;
         return(true);
     }
     systemIdentityToUse = Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite ? AuthenticationContext.SystemIdentity : null;
     return(false);
 }
コード例 #5
0
 /// <summary>
 /// Construct an <see cref="AuthenticationContext"/>
 /// </summary>
 /// <param name="systemIdentity">The value of <see cref="SystemIdentity"/></param>
 /// <param name="user">The value of <see cref="User"/></param>
 /// <param name="instanceUser">The value of <see cref="InstancePermissionSet"/></param>
 public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstancePermissionSet instanceUser)
 {
     User = user ?? throw new ArgumentNullException(nameof(user));
     if (systemIdentity == null && User.SystemIdentifier != null)
     {
         throw new ArgumentNullException(nameof(systemIdentity));
     }
     PermissionSet = user.PermissionSet
                     ?? user.Group.PermissionSet
                     ?? throw new ArgumentException("No PermissionSet provider", nameof(user));
     InstancePermissionSet = instanceUser;
     SystemIdentity        = systemIdentity;
 }
コード例 #6
0
        /// <inheritdoc />
        public async Task <bool> CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
        {
            await EnsureDirectories(cancellationToken).ConfigureAwait(false);

            var path = ValidateConfigRelativePath(configurationRelativePath);

            bool?result = null;

            void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken);

            if (systemIdentity == null)
            {
                await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
            }
            else
            {
                await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false);
            }

            return(result.Value);
        }
コード例 #7
0
        /// <inheritdoc />
        public async Task <bool> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
        {
            await EnsureDirectories(cancellationToken).ConfigureAwait(false);

            var path = ValidateConfigRelativePath(configurationRelativePath);

            var result = false;

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
            {
                void CheckDeleteImpl() => result = synchronousIOManager.DeleteDirectory(path);

                if (systemIdentity != null)
                {
                    await systemIdentity.RunImpersonated(CheckDeleteImpl, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    CheckDeleteImpl();
                }
            }
            return(result);
        }
コード例 #8
0
 /// <inheritdoc />
 public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry)
 {
     if (user == null)
     {
         throw new ArgumentNullException(nameof(user));
     }
     if (systemIdentity == null)
     {
         throw new ArgumentNullException(nameof(systemIdentity));
     }
     lock (cachedIdentities)
     {
         if (cachedIdentities.TryGetValue(user.Id, out var identCache))
         {
             identCache.Dispose();                       //also clears it out
         }
         identCache = new IdentityCacheObject(systemIdentity.Clone(), () =>
         {
             lock (cachedIdentities)
                 cachedIdentities.Remove(user.Id);
         }, expiry);
         cachedIdentities.Add(user.Id, identCache);
     }
 }
コード例 #9
0
        /// <inheritdoc />
        public async Task <ConfigurationFile> Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, string previousHash, CancellationToken cancellationToken)
        {
            await EnsureDirectories(cancellationToken).ConfigureAwait(false);

            var path = ValidateConfigRelativePath(configurationRelativePath);

            ConfigurationFile result = null;

            void WriteImpl()
            {
                lock (this)
                    try
                    {
                        var fileHash = previousHash;
                        var success  = synchronousIOManager.WriteFileChecked(path, data, ref fileHash, cancellationToken);
                        if (!success)
                        {
                            return;
                        }
                        result = new ConfigurationFile
                        {
                            Content      = data,
                            IsDirectory  = false,
                            LastReadHash = fileHash,
                            AccessDenied = false,
                            Path         = configurationRelativePath
                        };
                    }
                    catch (UnauthorizedAccessException)
                    {
                        //this happens on windows, dunno about linux
                        bool isDirectory;
                        try
                        {
                            isDirectory = synchronousIOManager.IsDirectory(path);
                        }
                        catch
                        {
                            isDirectory = false;
                        }

                        result = new ConfigurationFile
                        {
                            Path = configurationRelativePath
                        };
                        if (!isDirectory)
                        {
                            result.AccessDenied = true;
                        }
                        else
                        {
                            result.IsDirectory = true;
                        }
                    }
            }

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
                if (systemIdentity == null)
                {
                    await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
                }
                else
                {
                    await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false);
                }

            return(result);
        }
コード例 #10
0
        /// <inheritdoc />
        public async Task <ConfigurationFile> Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
        {
            await EnsureDirectories(cancellationToken).ConfigureAwait(false);

            var path = ValidateConfigRelativePath(configurationRelativePath);

            ConfigurationFile result = null;

            void ReadImpl()
            {
                lock (this)
                    try
                    {
                        var    content = synchronousIOManager.ReadFile(path);
                        string sha1String;
#pragma warning disable CA5350                                                                                       // Do not use insecure cryptographic algorithm SHA1.
                        using (var sha1 = new SHA1Managed())
                                                                                      #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1.
                            sha1String = String.Join("", sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
                        result = new ConfigurationFile
                        {
                            Content      = content,
                            IsDirectory  = false,
                            LastReadHash = sha1String,
                            AccessDenied = false,
                            Path         = configurationRelativePath
                        };
                    }
                    catch (UnauthorizedAccessException)
                    {
                        //this happens on windows, dunno about linux
                        bool isDirectory;
                        try
                        {
                            isDirectory = synchronousIOManager.IsDirectory(path);
                        }
                        catch
                        {
                            isDirectory = false;
                        }

                        result = new ConfigurationFile
                        {
                            Path = configurationRelativePath
                        };
                        if (!isDirectory)
                        {
                            result.AccessDenied = true;
                        }
                        else
                        {
                            result.IsDirectory = true;
                        }
                    }
            }

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
                if (systemIdentity == null)
                {
                    await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
                }
                else
                {
                    await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false);
                }

            return(result);
        }
コード例 #11
0
        /// <inheritdoc />
        public async Task <IReadOnlyList <ConfigurationFile> > ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
        {
            await EnsureDirectories(cancellationToken).ConfigureAwait(false);

            var path = ValidateConfigRelativePath(configurationRelativePath);

            if (configurationRelativePath == null)
            {
                configurationRelativePath = "/";
            }

            List <ConfigurationFile> result = new List <ConfigurationFile>();

            void ListImpl()
            {
                var enumerator = synchronousIOManager.GetDirectories(path, cancellationToken);

                try
                {
                    result.AddRange(enumerator.Select(x => new ConfigurationFile
                    {
                        IsDirectory = true,
                        Path        = ioManager.ConcatPath(configurationRelativePath, x),
                    }));
                }
                catch (IOException e)
                {
                    logger.LogDebug("IOException while writing {0}: {1}", path, e);
                    result = null;
                    return;
                }
                enumerator = synchronousIOManager.GetFiles(path, cancellationToken);
                result.AddRange(enumerator.Select(x => new ConfigurationFile
                {
                    IsDirectory = false,
                    Path        = ioManager.ConcatPath(configurationRelativePath, x),
                }));
            }

            using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
                if (systemIdentity == null)
                {
                    ListImpl();
                }
                else
                {
                    await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false);
                }

            return(result);
        }
コード例 #12
0
#pragma warning disable CA1506 // TODO: Decomplexify
        public async Task <IActionResult> CreateToken(CancellationToken cancellationToken)
        {
            if (ApiHeaders == null)
            {
                Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues("basic realm=\"Create TGS4 bearer token\""));
                return(HeadersIssue(false));
            }

            if (ApiHeaders.IsTokenAuthentication)
            {
                return(BadRequest(new ErrorMessage(ErrorCode.TokenWithToken)));
            }

            var oAuthLogin = ApiHeaders.OAuthProvider.HasValue;

            ISystemIdentity systemIdentity = null;

            if (!oAuthLogin)
            {
                try
                {
                    // trust the system over the database because a user's name can change while still having the same SID
                    systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
                }
                catch (NotImplementedException ex)
                {
                    Logger.LogTrace(ex, "System identities not implemented!");
                }
            }

            using (systemIdentity)
            {
                // Get the user from the database
                IQueryable <Models.User> query = DatabaseContext.Users.AsQueryable();
                if (oAuthLogin)
                {
                    string externalUserId;
                    try
                    {
                        var validator = oAuthProviders
                                        .GetValidator(ApiHeaders.OAuthProvider.Value);

                        if (validator == null)
                        {
                            return(BadRequest(new ErrorMessage(ErrorCode.OAuthProviderDisabled)));
                        }

                        externalUserId = await validator
                                         .ValidateResponseCode(ApiHeaders.Token, cancellationToken)
                                         .ConfigureAwait(false);
                    }
                    catch (RateLimitExceededException ex)
                    {
                        return(RateLimit(ex));
                    }

                    if (externalUserId == null)
                    {
                        return(Unauthorized());
                    }

                    query = query.Where(
                        x => x.OAuthConnections.Any(
                            y => y.Provider == ApiHeaders.OAuthProvider.Value &&
                            y.ExternalUserId == externalUserId));
                }
                else
                {
                    string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username);
                    if (systemIdentity == null)
                    {
                        query = query.Where(x => x.CanonicalName == canonicalName);
                    }
                    else
                    {
                        query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
                    }
                }

                var users = await query.Select(x => new Models.User
                {
                    Id           = x.Id,
                    PasswordHash = x.PasswordHash,
                    Enabled      = x.Enabled,
                    Name         = x.Name
                }).ToListAsync(cancellationToken).ConfigureAwait(false);

                // Pick the DB user first
                var user = users
                           .OrderByDescending(dbUser => dbUser.PasswordHash != null)
                           .FirstOrDefault();

                // No user? You're not allowed
                if (user == null)
                {
                    return(Unauthorized());
                }

                // A system user may have had their name AND password changed to one in our DB...
                // Or a DB user was created that had the same user/pass as a system user
                // Dumb admins...
                // FALLBACK TO THE DB USER HERE, DO NOT REVEAL A SYSTEM LOGIN!!!
                // This of course, allows system users to discover TGS users in this (HIGHLY IMPROBABLE) case but that is not our fault
                var  originalHash        = user.PasswordHash;
                var  isDbUser            = originalHash != null;
                bool usingSystemIdentity = systemIdentity != null && !isDbUser;
                if (!oAuthLogin)
                {
                    if (!usingSystemIdentity)
                    {
                        // DB User password check and update
                        if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
                        {
                            return(Unauthorized());
                        }
                        if (user.PasswordHash != originalHash)
                        {
                            Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id);
                            var updatedUser = new Models.User
                            {
                                Id = user.Id
                            };
                            DatabaseContext.Users.Attach(updatedUser);
                            updatedUser.PasswordHash = user.PasswordHash;
                            await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
                        }
                    }
                    else if (systemIdentity.Username != user.Name)
                    {
                        // System identity username change update
                        Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id);
                        DatabaseContext.Users.Attach(user);
                        user.Name          = systemIdentity.Username;
                        user.CanonicalName = Models.User.CanonicalizeName(user.Name);
                        await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
                    }
                }

                // Now that the bookeeping is done, tell them to f**k off if necessary
                if (!user.Enabled.Value)
                {
                    Logger.LogTrace("Not logging in disabled user {0}.", user.Id);
                    return(Forbid());
                }

                var token = await tokenFactory.CreateToken(user, oAuthLogin, cancellationToken).ConfigureAwait(false);

                if (usingSystemIdentity)
                {
                    // expire the identity slightly after the auth token in case of lag
                    var identExpiry = token.ExpiresAt;
                    identExpiry += tokenFactory.ValidationParameters.ClockSkew;
                    identExpiry += TimeSpan.FromSeconds(15);
                    identityCache.CacheSystemIdentity(user, systemIdentity, identExpiry);
                }

                Logger.LogDebug("Successfully logged in user {0}!", user.Id);

                return(Json(token));
            }
        }