From 86bec3368bc6f6fa1ede26dadc9b82f5c89a5b2f Mon Sep 17 00:00:00 2001 From: Paul Greenberg Date: Fri, 22 Mar 2024 11:53:01 -0400 Subject: [PATCH] add api key testing to profile api --- pkg/authn/api_test_user_api_key.go | 83 ++++++++++++++++++++++++++++++ pkg/authn/handle_api_profile.go | 3 ++ pkg/identity/api_key.go | 42 +++++++++------ pkg/identity/mfa_token.go | 4 +- pkg/ids/local/store.go | 5 +- 5 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 pkg/authn/api_test_user_api_key.go diff --git a/pkg/authn/api_test_user_api_key.go b/pkg/authn/api_test_user_api_key.go new file mode 100644 index 0000000..c5b14ca --- /dev/null +++ b/pkg/authn/api_test_user_api_key.go @@ -0,0 +1,83 @@ +// Copyright 2024 Paul Greenberg greenpau@outlook.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authn + +import ( + "context" + "net/http" + + "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" + "github.com/greenpau/go-authcrunch/pkg/identity" + "github.com/greenpau/go-authcrunch/pkg/ids" + "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/user" +) + +// TestUserAPIKey tests API key. +func (p *Portal) TestUserAPIKey( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + rr *requests.Request, + parsedUser *user.User, + resp map[string]interface{}, + usr *user.User, + backend ids.IdentityStore, + bodyData map[string]interface{}) error { + + rr.Key.Usage = "api" + + var keyContent string + + // Extract data. + if v, exists := bodyData["id"]; exists { + switch keyID := v.(type) { + case string: + rr.Key.ID = keyID + default: + resp["message"] = "Profile API did find key id in the request payload, but it is malformed" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + } else { + resp["message"] = "Profile API did not find key id in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + if v, exists := bodyData["content"]; exists { + switch exp := v.(type) { + case string: + keyContent = exp + default: + resp["message"] = "Profile API did find key content in the request payload, but it is malformed" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + } else { + resp["message"] = "Profile API did not find key content in the request payload" + return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) + } + + if err := backend.Request(operator.GetAPIKey, rr); err != nil { + resp["message"] = "Profile API failed to get API key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + apiKey := rr.Response.Payload.(*identity.APIKey) + + if !apiKey.Match(keyContent) { + resp["message"] = "Profile API failed to validate provided API key" + return handleAPIProfileResponse(w, rr, http.StatusInternalServerError, resp) + } + + resp["entry"] = "OK" + return handleAPIProfileResponse(w, rr, http.StatusOK, resp) +} diff --git a/pkg/authn/handle_api_profile.go b/pkg/authn/handle_api_profile.go index 8abfb76..831237e 100644 --- a/pkg/authn/handle_api_profile.go +++ b/pkg/authn/handle_api_profile.go @@ -106,6 +106,7 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r case "fetch_user_api_key": case "delete_user_api_key": case "add_user_api_key": + case "test_user_api_key": case "fetch_user_ssh_keys": case "fetch_user_ssh_key": case "delete_user_ssh_key": @@ -201,6 +202,8 @@ func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r return p.DeleteUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) case "add_user_api_key": return p.AddUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) + case "test_user_api_key": + return p.TestUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData) case "fetch_user_ssh_keys": return p.FetchUserSSHKeys(ctx, w, r, rr, parsedUser, resp, usr, backend) case "fetch_user_ssh_key": diff --git a/pkg/identity/api_key.go b/pkg/identity/api_key.go index aba2023..4f78e90 100644 --- a/pkg/identity/api_key.go +++ b/pkg/identity/api_key.go @@ -15,11 +15,13 @@ package identity import ( + "time" + "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/tagging" "github.com/greenpau/go-authcrunch/pkg/util" "golang.org/x/crypto/bcrypt" - "time" ) // APIKeyBundle is a collection of API keys. @@ -30,16 +32,19 @@ type APIKeyBundle struct { // APIKey is an API key. type APIKey struct { - ID string `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"` - Prefix string `json:"prefix,omitempty" xml:"prefix,omitempty" yaml:"prefix,omitempty"` - Usage string `json:"usage,omitempty" xml:"usage,omitempty" yaml:"usage,omitempty"` - Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` - Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` - Expired bool `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"` - ExpiredAt time.Time `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"` - Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` - DisabledAt time.Time `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` + ID string `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"` + Prefix string `json:"prefix,omitempty" xml:"prefix,omitempty" yaml:"prefix,omitempty"` + Usage string `json:"usage,omitempty" xml:"usage,omitempty" yaml:"usage,omitempty"` + Comment string `json:"comment,omitempty" xml:"comment,omitempty" yaml:"comment,omitempty"` + Payload string `json:"payload,omitempty" xml:"payload,omitempty" yaml:"payload,omitempty"` + Expired bool `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"` + ExpiredAt time.Time `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"` + Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` + DisabledAt time.Time `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` + Description string `json:"description,omitempty" xml:"description,omitempty" yaml:"description,omitempty"` + Tags []tagging.Tag `json:"tags,omitempty" xml:"tags,omitempty" yaml:"tags,omitempty"` + Labels []string `json:"labels,omitempty" xml:"labels,omitempty" yaml:"labels,omitempty"` } // NewAPIKeyBundle returns an instance of APIKeyBundle. @@ -80,12 +85,15 @@ func NewAPIKey(r *requests.Request) (*APIKey, error) { return nil, errors.ErrAPIKeyCommentEmpty } p := &APIKey{ - Comment: r.Key.Comment, - ID: util.GetRandomString(40), - Prefix: r.Key.Prefix, - Payload: r.Key.Payload, - Usage: r.Key.Usage, - CreatedAt: time.Now().UTC(), + Comment: r.Key.Comment, + ID: util.GetRandomString(40), + Prefix: r.Key.Prefix, + Payload: r.Key.Payload, + Usage: r.Key.Usage, + CreatedAt: time.Now().UTC(), + Description: r.Key.Description, + Tags: r.Key.Tags, + Labels: r.Key.Labels, } if r.Key.Disabled { p.Disabled = true diff --git a/pkg/identity/mfa_token.go b/pkg/identity/mfa_token.go index edbac5f..418284e 100644 --- a/pkg/identity/mfa_token.go +++ b/pkg/identity/mfa_token.go @@ -425,7 +425,7 @@ func (p *MfaToken) WebAuthnRequest(payload string) (*WebAuthnAuthenticateRequest } // Verify that the value of C.crossOrigin is false. - if r.ClientData.CrossOrigin == true { + if r.ClientData.CrossOrigin { return r, errors.ErrWebAuthnRequest.WithArgs("client data cross origin true is not supported") } @@ -438,7 +438,7 @@ func (p *MfaToken) WebAuthnRequest(payload string) (*WebAuthnAuthenticateRequest } // Verify that the User Present bit of the flags in authData is set. - if r.AuthData.Flags["UP"] != true { + if !r.AuthData.Flags["UP"] { return r, errors.ErrWebAuthnRequest.WithArgs("authData User Present bit is not set") } diff --git a/pkg/ids/local/store.go b/pkg/ids/local/store.go index 6512791..0219ea0 100644 --- a/pkg/ids/local/store.go +++ b/pkg/ids/local/store.go @@ -122,7 +122,6 @@ func (b *IdentityStore) Request(op operator.Type, r *requests.Request) error { case operator.DeletePublicKey: return b.authenticator.DeletePublicKey(r) case operator.AddMfaToken: - // b.logger.Debug("detected supported identity store operation", zap.Any("op", op), zap.Any("params", r)) return b.authenticator.AddMfaToken(r) case operator.DeleteMfaToken: return b.authenticator.DeleteMfaToken(r) @@ -132,8 +131,12 @@ func (b *IdentityStore) Request(op operator.Type, r *requests.Request) error { return b.authenticator.DeleteAPIKey(r) case operator.GetPublicKeys: return b.authenticator.GetPublicKeys(r) + case operator.GetPublicKey: + return b.authenticator.GetPublicKey(r) case operator.GetAPIKeys: return b.authenticator.GetAPIKeys(r) + case operator.GetAPIKey: + return b.authenticator.GetAPIKey(r) case operator.GetMfaTokens: return b.authenticator.GetMfaTokens(r) case operator.GetMfaToken: