Example #1
0
        public async Task Invoke(HttpContext context)
        {
            var requestUri = new Uri($"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}");
            var requestId  = Guid.NewGuid().ToString("D");

            bool isForm = false;

            if (context.Request.Method == HttpMethods.Post && context.Request.HasFormContentType)
            {
                _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request: {context.Request.Method} {requestUri.OriginalString} Form: {string.Join("|", context.Request.Form.Keys)}");
                isForm = true;
            }
            else
            {
                _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request: {context.Request.Method} {requestUri.OriginalString}");
            }

            if (isForm)
            {
                // https://docs.docker.com/registry/spec/auth/oauth/

                var service             = context.Request.Form["service"].OneOrDefault();
                var scope               = context.Request.Form["scope"].OneOrDefault();
                var accessType          = context.Request.Form["access_type"].OneOrDefault();
                var includeRefreshToken = string.Equals("offline", accessType, StringComparison.Ordinal);
                if (!string.IsNullOrEmpty(service))
                {
                    if (string.Equals("refresh_token", context.Request.Form["grant_type"], StringComparison.Ordinal))
                    {
                        var refreshToken = context.Request.Form["refresh_token"].OneOrDefault();
                        if (!string.IsNullOrEmpty(refreshToken))
                        {
                            //var ecdsa = ECDsa.Create();
                            //ecdsa.ImportPkcs8PrivateKey(_settings.TokenAuthentication.EccKey, out _);

                            //var parameters = new TokenValidationParameters {
                            //	LifetimeValidator = (before, expires, token, parameters) => expires > DateTime.UtcNow,
                            //	ValidateAudience = false,
                            //	ValidateIssuer = false,
                            //	ValidateActor = false,
                            //	ValidateLifetime = true,
                            //	IssuerSigningKey = new ECDsaSecurityKey(ecdsa)
                            //};

                            //var handler = new JwtSecurityTokenHandler();
                            //var identity = handler.ValidateToken(refreshToken, parameters, out var token);

                            var tokenParts = refreshToken.Split('.');
                            if (tokenParts.Length == 3)
                            {
                                var header       = CommonUtility.Base64UrlDecode(tokenParts[0]);
                                var body         = CommonUtility.Base64UrlDecode(tokenParts[1]);
                                var signature    = CommonUtility.Base64UrlDecode(tokenParts[2]);
                                var headerObject = JsonSerializer.Deserialize <JwtHeader>(header);
                                if (headerObject == null || headerObject.Algorithm?.StartsWith("ES", StringComparison.Ordinal) != true || !string.Equals("JWT", headerObject.TokenType))
                                {
                                    _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid token received");
                                }
                                else
                                {
                                    var  toVerify = Encoding.UTF8.GetBytes(tokenParts[0] + "." + tokenParts[1]);
                                    bool valid    = false;
                                    using (var ecdsa = ECDsa.Create()) {
                                        ecdsa.ImportPkcs8PrivateKey(_settings.TokenAuthentication.EccKey, out _);
                                        if (headerObject.Algorithm == "ES256")
                                        {
                                            valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA256);
                                        }
                                        else if (headerObject.Algorithm == "ES384")
                                        {
                                            valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA384);
                                        }
                                        else if (headerObject.Algorithm == "ES512")
                                        {
                                            valid = ecdsa.VerifyData(toVerify, signature, HashAlgorithmName.SHA512);
                                        }
                                    }
                                    if (!valid)
                                    {
                                        _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid token received");
                                    }
                                    else
                                    {
                                        var tokenInfo = ReadTokenBody(body);
                                        var username  = tokenInfo.claims.OneOrDefault(z => z.Type == ClaimsIdentity.DefaultNameClaimType);
                                        if (username != null)
                                        {
                                            var account = _settings.TokenAuthentication.Accounts.FirstOrDefault(z
                                                                                                                => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) &&
                                                                                                                string.Equals(z.Username, username.Value, StringComparison.OrdinalIgnoreCase));
                                            if (account != null)
                                            {
                                                await IssueToken(context, requestId, account, scope.Split(' ').ToList(), includeRefreshToken, refreshToken, true);

                                                return;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else if (string.Equals("password", context.Request.Form["grant_type"], StringComparison.Ordinal))
                    {
                        var username = context.Request.Form["username"].OneOrDefault();
                        var password = context.Request.Form["password"].OneOrDefault();

                        var account = _settings.TokenAuthentication.Accounts.FirstOrDefault(z
                                                                                            => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) &&
                                                                                            string.Equals(z.Username, username, StringComparison.OrdinalIgnoreCase) &&
                                                                                            string.Equals(z.Password, password, StringComparison.Ordinal));
                        if (account != null)
                        {
                            await IssueToken(context, requestId, account, scope.Split(' ').ToList(), includeRefreshToken, null, true);

                            return;
                        }
                        else
                        {
                            _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid credentials received");
                        }
                    }
                }
            }
            else
            {
                // https://docs.docker.com/registry/spec/auth/token/

                string authorization = context.Request.Headers["Authorization"];
                string service       = context.Request.Query["service"];
                string offline_token = context.Request.Query["offline_token"];
                if (authorization != null && authorization.StartsWith("Basic ", StringComparison.Ordinal) && !string.IsNullOrEmpty(service))
                {
                    var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authorization.Substring(6)));
                    int x           = credentials.IndexOf(':');
                    if (x > 0)
                    {
                        string user     = credentials.Substring(0, x);
                        string password = credentials.Substring(x + 1);
                        var    account  = _settings.TokenAuthentication.Accounts.FirstOrDefault(z
                                                                                                => string.Equals(z.Registry, service, StringComparison.OrdinalIgnoreCase) &&
                                                                                                string.Equals(z.Username, user, StringComparison.OrdinalIgnoreCase) &&
                                                                                                string.Equals(z.Password, password, StringComparison.Ordinal));
                        if (account != null)
                        {
                            var includeRefreshToken = string.Equals("true", offline_token, StringComparison.Ordinal);
                            await IssueToken(context, requestId, account, context.Request.Query["scope"].ToList(), includeRefreshToken, null, false);

                            return;
                        }
                        else
                        {
                            _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Invalid credentials received");
                        }
                    }
                }
            }

            context.Response.StatusCode = 401;
            context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Docker token authentication\", charset=\"UTF-8\"";
            _logger.LogInformation($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{requestId}] Request authorization required");
            return;
            //await _nextMiddleware(context);
        }