ssh public keys: db and api (#2058)

ui/offscreen-diff-contents
Marko Gaćeša 2024-05-27 14:26:21 +00:00 committed by Harness
parent 8d5c83d035
commit 0d72a20450
26 changed files with 1277 additions and 26 deletions

View File

@ -34,6 +34,7 @@ type Controller struct {
principalStore store.PrincipalStore
tokenStore store.TokenStore
membershipStore store.MembershipStore
publicKeyStore store.PublicKeyStore
}
func NewController(
@ -43,6 +44,7 @@ func NewController(
principalStore store.PrincipalStore,
tokenStore store.TokenStore,
membershipStore store.MembershipStore,
publicKeyStore store.PublicKeyStore,
) *Controller {
return &Controller{
tx: tx,
@ -51,6 +53,7 @@ func NewController(
principalStore: principalStore,
tokenStore: tokenStore,
membershipStore: membershipStore,
publicKeyStore: publicKeyStore,
}
}

View File

@ -0,0 +1,119 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"context"
"fmt"
"strings"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/services/publickey"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
type CreatePublicKeyInput struct {
Identifier string `json:"identifier"`
Usage enum.PublicKeyUsage `json:"usage"`
Content string `json:"content"`
}
func (c *Controller) CreatePublicKey(
ctx context.Context,
session *auth.Session,
userUID string,
in *CreatePublicKeyInput,
) (*types.PublicKey, error) {
user, err := c.principalStore.FindUserByUID(ctx, userUID)
if err != nil {
return nil, fmt.Errorf("failed to fetch user by uid: %w", err)
}
if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserEdit); err != nil {
return nil, err
}
if err := sanitizeCreatePublicKeyInput(in); err != nil {
return nil, err
}
key, comment, err := publickey.ParseString(in.Content)
if err != nil {
return nil, errors.InvalidArgument("could not parse public key")
}
now := time.Now().UnixMilli()
k := &types.PublicKey{
PrincipalID: user.ID,
Created: now,
Verified: nil, // the key is created as unverified
Identifier: in.Identifier,
Usage: in.Usage,
Fingerprint: key.Fingerprint(),
Content: in.Content,
Comment: comment,
Type: key.Type(),
}
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
existingKeys, err := c.publicKeyStore.ListByFingerprint(ctx, k.Fingerprint)
if err != nil {
return fmt.Errorf("failed to read keys by fingerprint: %w", err)
}
for _, existingKey := range existingKeys {
if key.Matches(existingKey.Content) {
return errors.InvalidArgument("Key is already in use")
}
}
err = c.publicKeyStore.Create(ctx, k)
if err != nil {
return fmt.Errorf("failed to insert public key: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return k, nil
}
func sanitizeCreatePublicKeyInput(in *CreatePublicKeyInput) error {
if err := check.Identifier(in.Identifier); err != nil {
return err
}
usage, ok := in.Usage.Sanitize()
if !ok {
return errors.InvalidArgument("invalid value for public key usage")
}
in.Usage = usage
in.Content = strings.TrimSpace(in.Content)
if in.Content == "" {
return errors.InvalidArgument("public key not provided")
}
return nil
}

View File

@ -0,0 +1,47 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) DeletePublicKey(
ctx context.Context,
session *auth.Session,
userUID string,
identifier string,
) error {
user, err := c.principalStore.FindUserByUID(ctx, userUID)
if err != nil {
return fmt.Errorf("failed to fetch user by uid: %w", err)
}
if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserEdit); err != nil {
return err
}
err = c.publicKeyStore.DeleteByIdentifier(ctx, user.ID, identifier)
if err != nil {
return fmt.Errorf("failed to delete public key by id: %w", err)
}
return nil
}

View File

@ -0,0 +1,71 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) ListPublicKeys(
ctx context.Context,
session *auth.Session,
userUID string,
filter *types.PublicKeyFilter,
) ([]types.PublicKey, int, error) {
user, err := c.principalStore.FindUserByUID(ctx, userUID)
if err != nil {
return nil, 0, fmt.Errorf("failed to fetch user by uid: %w", err)
}
if err = apiauth.CheckUser(ctx, c.authorizer, session, user, enum.PermissionUserView); err != nil {
return nil, 0, err
}
var (
list []types.PublicKey
count int
)
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
list, err = c.publicKeyStore.List(ctx, user.ID, filter)
if err != nil {
return fmt.Errorf("failed to list public keys for user: %w", err)
}
if filter.Page == 1 && len(list) < filter.Size {
count = len(list)
return nil
}
count, err = c.publicKeyStore.Count(ctx, user.ID, filter)
if err != nil {
return fmt.Errorf("failed to count public keys for user: %w", err)
}
return nil
}, dbtx.TxDefaultReadOnly)
if err != nil {
return nil, 0, err
}
return list, count, nil
}

View File

@ -35,6 +35,7 @@ func ProvideController(
principalStore store.PrincipalStore,
tokenStore store.TokenStore,
membershipStore store.MembershipStore,
publicKeyStore store.PublicKeyStore,
) *Controller {
return NewController(
tx,
@ -42,5 +43,6 @@ func ProvideController(
authorizer,
principalStore,
tokenStore,
membershipStore)
membershipStore,
publicKeyStore)
}

View File

@ -0,0 +1,47 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/user"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleCreatePublicKey(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID := session.Principal.UID
in := new(user.CreatePublicKeyInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
key, err := userCtrl.CreatePublicKey(ctx, session, userUID, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusCreated, key)
}
}

View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"net/http"
"github.com/harness/gitness/app/api/controller/user"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleDeletePublicKey(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID := session.Principal.UID
id, err := request.GetPublicKeyIdentifierFromPath(r)
if err != nil {
render.BadRequest(ctx, w)
return
}
err = userCtrl.DeletePublicKey(ctx, session, userUID, id)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,46 @@
// Copyright 2023 Harness, Inc.
//
// 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 user
import (
"net/http"
"github.com/harness/gitness/app/api/controller/user"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleListPublicKeys(userCtrl *user.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
userUID := session.Principal.UID
filter, err := request.ParseListPublicKeyQueryFilterFromRequest(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
keys, count, err := userCtrl.ListPublicKeys(ctx, session, userUID, &filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.Pagination(r, w, filter.Page, filter.Size, count)
render.JSON(w, http.StatusOK, keys)
}
}

View File

@ -61,6 +61,36 @@ var queryParameterSortMembershipSpaces = openapi3.ParameterOrRef{
},
}
var queryParameterQueryPublicKey = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamQuery,
In: openapi3.ParameterInQuery,
Description: ptr.String("The substring which is used to filter the public keys by their path identifier."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
},
},
},
}
var queryParameterSortPublicKey = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamSort,
In: openapi3.ParameterInQuery,
Description: ptr.String("The data by which the public keys are sorted."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeString),
Default: ptrptr(enum.PublicKeySortCreated),
Enum: enum.PublicKeySort("").Enum(),
},
},
},
}
// helper function that constructs the openapi specification
// for user account resources.
func buildUser(reflector *openapi3.Reflector) {
@ -99,4 +129,35 @@ func buildUser(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opMemberSpaces, new([]types.MembershipSpace), http.StatusOK)
_ = reflector.SetJSONResponse(&opMemberSpaces, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.Spec.AddOperation(http.MethodGet, "/user/memberships", opMemberSpaces)
opKeyCreate := openapi3.Operation{}
opKeyCreate.WithTags("user")
opKeyCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createPublicKey"})
_ = reflector.SetRequest(&opKeyCreate, new(user.CreatePublicKeyInput), http.MethodPost)
_ = reflector.SetJSONResponse(&opKeyCreate, new(types.PublicKey), http.StatusCreated)
_ = reflector.SetJSONResponse(&opKeyCreate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opKeyCreate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.Spec.AddOperation(http.MethodPost, "/user/keys", opKeyCreate)
opKeyDelete := openapi3.Operation{}
opKeyDelete.WithTags("user")
opKeyDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deletePublicKey"})
_ = reflector.SetRequest(&opKeyDelete, struct {
ID string `path:"public_key_identifier"`
}{}, http.MethodDelete)
_ = reflector.SetJSONResponse(&opKeyDelete, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opKeyDelete, new(usererror.Error), http.StatusNotFound)
_ = reflector.SetJSONResponse(&opKeyDelete, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/user/keys/{public_key_identifier}", opKeyDelete)
opKeyList := openapi3.Operation{}
opKeyList.WithTags("user")
opKeyList.WithMapOfAnything(map[string]interface{}{"operationId": "listPublicKey"})
opKeyList.WithParameters(queryParameterPage, queryParameterLimit,
queryParameterQueryPublicKey, queryParameterSortPublicKey, queryParameterOrder)
_ = reflector.SetRequest(&opKeyList, struct{}{}, http.MethodGet)
_ = reflector.SetJSONResponse(&opKeyList, new([]types.PublicKey), http.StatusOK)
_ = reflector.SetJSONResponse(&opKeyList, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opKeyList, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.Spec.AddOperation(http.MethodGet, "/user/keys", opKeyList)
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Harness, Inc.
//
// 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 request
import (
"net/http"
"net/url"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
const (
PathParamPublicKeyIdentifier = "public_key_identifier"
)
func GetPublicKeyIdentifierFromPath(r *http.Request) (string, error) {
identifier, err := PathParamOrError(r, PathParamPublicKeyIdentifier)
if err != nil {
return "", err
}
// paths are unescaped
return url.PathUnescape(identifier)
}
// ParseListPublicKeyQueryFilterFromRequest parses query filter for public keys from the url.
func ParseListPublicKeyQueryFilterFromRequest(r *http.Request) (types.PublicKeyFilter, error) {
sort := enum.PublicKeySort(ParseSort(r))
sort, ok := sort.Sanitize()
if !ok {
return types.PublicKeyFilter{}, usererror.BadRequest("Invalid value for the sort query parameter.")
}
return types.PublicKeyFilter{
ListQueryFilter: ParseListQueryFilterFromRequest(r),
Sort: sort,
Order: ParseOrder(r),
}, nil
}

View File

@ -628,6 +628,14 @@ func setupUser(r chi.Router, userCtrl *user.Controller) {
r.Delete("/", handleruser.HandleDeleteToken(userCtrl, enum.TokenTypeSession))
})
})
// Private keys
r.Route("/keys", func(r chi.Router) {
r.Get("/", handleruser.HandleListPublicKeys(userCtrl))
r.Post("/", handleruser.HandleCreatePublicKey(userCtrl))
r.Delete(fmt.Sprintf("/{%s}", request.PathParamPublicKeyIdentifier),
handleruser.HandleDeletePublicKey(userCtrl))
})
})
}

View File

@ -0,0 +1,100 @@
// Copyright 2023 Harness, Inc.
//
// 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 publickey
import (
"crypto/sha256"
"encoding/base64"
"github.com/harness/gitness/errors"
"github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
)
var AllowedTypes = []string{
gossh.KeyAlgoRSA,
gossh.KeyAlgoECDSA256,
gossh.KeyAlgoECDSA384,
gossh.KeyAlgoECDSA521,
gossh.KeyAlgoED25519,
gossh.KeyAlgoSKECDSA256,
gossh.KeyAlgoSKED25519,
}
var DisallowedTypes = []string{
gossh.KeyAlgoDSA,
}
func From(key gossh.PublicKey) KeyInfo {
return KeyInfo{
Key: key,
}
}
func ParseString(keyData string) (KeyInfo, string, error) {
return Parse([]byte(keyData))
}
func Parse(keyData []byte) (KeyInfo, string, error) {
publicKey, comment, _, _, err := gossh.ParseAuthorizedKey(keyData)
if err != nil {
return KeyInfo{}, "", err
}
keyType := publicKey.Type()
// explicitly disallowed
if slices.Contains(DisallowedTypes, keyType) {
return KeyInfo{}, "", errors.InvalidArgument("keys of type %s are not allowed", keyType)
}
// only allowed
if !slices.Contains(AllowedTypes, keyType) {
return KeyInfo{}, "", errors.InvalidArgument("allowed key types are %v", AllowedTypes)
}
return KeyInfo{
Key: publicKey,
}, comment, nil
}
type KeyInfo struct {
Key gossh.PublicKey
}
func (key KeyInfo) Matches(s string) bool {
otherKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(s))
if err != nil {
return false
}
return key.MatchesKey(otherKey)
}
func (key KeyInfo) MatchesKey(otherKey gossh.PublicKey) bool {
return ssh.KeysEqual(key.Key, otherKey)
}
func (key KeyInfo) Fingerprint() string {
sum := sha256.New()
sum.Write(key.Key.Marshal())
return base64.StdEncoding.EncodeToString(sum.Sum(nil))
}
func (key KeyInfo) Type() string {
return key.Key.Type()
}

View File

@ -0,0 +1,91 @@
// Copyright 2023 Harness, Inc.
//
// 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 publickey
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/gliderlabs/ssh"
)
type Service interface {
ValidateKey(ctx context.Context, publicKey ssh.PublicKey, usage enum.PublicKeyUsage) (*types.PrincipalInfo, error)
}
func NewService(
publicKeyStore store.PublicKeyStore,
pCache store.PrincipalInfoCache,
) LocalService {
return LocalService{
publicKeyStore: publicKeyStore,
pCache: pCache,
}
}
type LocalService struct {
publicKeyStore store.PublicKeyStore
pCache store.PrincipalInfoCache
}
// ValidateKey tries to match the provided key to one of the keys in the database.
// It updates the verified timestamp of the matched key to mark it as used.
func (s LocalService) ValidateKey(
ctx context.Context,
publicKey ssh.PublicKey,
usage enum.PublicKeyUsage,
) (*types.PrincipalInfo, error) {
key := From(publicKey)
fingerprint := key.Fingerprint()
existingKeys, err := s.publicKeyStore.ListByFingerprint(ctx, fingerprint)
if err != nil {
return nil, fmt.Errorf("failed to read keys by fingerprint: %w", err)
}
var keyID int64
var principalID int64
for _, existingKey := range existingKeys {
if !key.Matches(existingKey.Content) || existingKey.Usage != usage {
continue
}
keyID = existingKey.ID
principalID = existingKey.PrincipalID
}
if keyID == 0 {
return nil, errors.NotFound("Unrecognized key")
}
pInfo, err := s.pCache.Get(ctx, principalID)
if err != nil {
return nil, fmt.Errorf("failed to pull principal info by public key's principal ID: %w", err)
}
err = s.publicKeyStore.MarkAsVerified(ctx, keyID, time.Now().UnixMilli())
if err != nil {
return nil, fmt.Errorf("failed mark key as verified: %w", err)
}
return pInfo, nil
}

View File

@ -0,0 +1,32 @@
// Copyright 2023 Harness, Inc.
//
// 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 publickey
import (
"github.com/harness/gitness/app/store"
"github.com/google/wire"
)
var WireSet = wire.NewSet(
ProvidePublicKey,
)
func ProvidePublicKey(
publicKeyStore store.PublicKeyStore,
pCache store.PrincipalInfoCache,
) Service {
return NewService(publicKeyStore, pCache)
}

View File

@ -806,4 +806,30 @@ type (
// FindByIdentifier returns a types.UserGroup given a space ID and identifier.
FindByIdentifier(ctx context.Context, spaceID int64, identifier string) (*types.UserGroup, error)
}
PublicKeyStore interface {
// Find returns a public key given an ID.
Find(ctx context.Context, id int64) (*types.PublicKey, error)
// FindByIdentifier returns a public key given a principal ID and an identifier.
FindByIdentifier(ctx context.Context, principalID int64, identifier string) (*types.PublicKey, error)
// Create creates a new public key.
Create(ctx context.Context, publicKey *types.PublicKey) error
// DeleteByIdentifier deletes a public key.
DeleteByIdentifier(ctx context.Context, principalID int64, identifier string) error
// MarkAsVerified updates the public key to mark it as verified.
MarkAsVerified(ctx context.Context, id int64, verified int64) error
// Count returns the number of public keys for the principal that match provided the filter.
Count(ctx context.Context, principalID int64, filter *types.PublicKeyFilter) (int, error)
// List returns the public keys for the principal that match provided the filter.
List(ctx context.Context, principalID int64, filter *types.PublicKeyFilter) ([]types.PublicKey, error)
// ListByFingerprint returns public keys given a fingerprint and key usage.
ListByFingerprint(ctx context.Context, fingerprint string) ([]types.PublicKey, error)
}
)

View File

@ -0,0 +1,3 @@
DROP INDEX public_keys_usage_fingerprint;
DROP INDEX public_keys_principal_id;
DROP TABLE public_keys;

View File

@ -0,0 +1,22 @@
CREATE TABLE public_keys (
public_key_id SERIAL PRIMARY KEY
,public_key_principal_id INTEGER NOT NULL
,public_key_created BIGINT NOT NULL
,public_key_verified BIGINT
,public_key_identifier TEXT NOT NULL
,public_key_usage TEXT NOT NULL
,public_key_fingerprint TEXT NOT NULL
,public_key_content TEXT NOT NULL
,public_key_comment TEXT NOT NULL
,public_key_type TEXT NOT NULL
,CONSTRAINT fk_public_key_principal_id FOREIGN KEY (public_key_principal_id)
REFERENCES principals (principal_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
CREATE INDEX public_keys_fingerprint
ON public_keys(public_key_fingerprint);
CREATE UNIQUE INDEX public_keys_principal_id_identifier
ON public_keys(public_key_principal_id, LOWER(public_key_identifier));

View File

@ -0,0 +1,3 @@
DROP INDEX public_keys_usage_fingerprint;
DROP INDEX public_keys_principal_id;
DROP TABLE public_keys;

View File

@ -0,0 +1,22 @@
CREATE TABLE public_keys (
public_key_id INTEGER PRIMARY KEY AUTOINCREMENT
,public_key_principal_id INTEGER NOT NULL
,public_key_created BIGINT NOT NULL
,public_key_verified BIGINT
,public_key_identifier TEXT NOT NULL
,public_key_usage TEXT NOT NULL
,public_key_fingerprint TEXT NOT NULL
,public_key_content TEXT NOT NULL
,public_key_comment TEXT NOT NULL
,public_key_type TEXT NOT NULL
,CONSTRAINT fk_public_key_principal_id FOREIGN KEY (public_key_principal_id)
REFERENCES principals (principal_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
CREATE INDEX public_keys_fingerprint
ON public_keys(public_key_fingerprint);
CREATE UNIQUE INDEX public_keys_principal_id_identifier
ON public_keys(public_key_principal_id, LOWER(public_key_identifier));

View File

@ -0,0 +1,356 @@
// Copyright 2023 Harness, Inc.
//
// 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 database
import (
"context"
"fmt"
"strings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
)
var _ store.PublicKeyStore = PublicKeyStore{}
// NewPublicKeyStore returns a new PublicKeyStore.
func NewPublicKeyStore(db *sqlx.DB) PublicKeyStore {
return PublicKeyStore{
db: db,
}
}
// PublicKeyStore implements a store.PublicKeyStore backed by a relational database.
type PublicKeyStore struct {
db *sqlx.DB
}
type publicKey struct {
ID int64 `db:"public_key_id"`
PrincipalID int64 `db:"public_key_principal_id"`
Created int64 `db:"public_key_created"`
Verified null.Int `db:"public_key_verified"`
Identifier string `db:"public_key_identifier"`
Usage string `db:"public_key_usage"`
Fingerprint string `db:"public_key_fingerprint"`
Content string `db:"public_key_content"`
Comment string `db:"public_key_comment"`
Type string `db:"public_key_type"`
}
const (
publicKeyColumns = `
public_key_id
,public_key_principal_id
,public_key_created
,public_key_verified
,public_key_identifier
,public_key_usage
,public_key_fingerprint
,public_key_content
,public_key_comment
,public_key_type`
publicKeySelectBase = `
SELECT` + publicKeyColumns + `
FROM public_keys`
)
// Find fetches a job by its unique identifier.
func (s PublicKeyStore) Find(ctx context.Context, id int64) (*types.PublicKey, error) {
const sqlQuery = publicKeySelectBase + `
WHERE public_key_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
result := &publicKey{}
if err := db.GetContext(ctx, result, sqlQuery, id); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find public key by id")
}
key := mapToPublicKey(result)
return &key, nil
}
// FindByIdentifier returns a public key given a principal ID and an identifier.
func (s PublicKeyStore) FindByIdentifier(
ctx context.Context,
principalID int64,
identifier string,
) (*types.PublicKey, error) {
const sqlQuery = publicKeySelectBase + `
WHERE public_key_principal_id = $1 and LOWER(public_key_identifier) = $2`
db := dbtx.GetAccessor(ctx, s.db)
result := &publicKey{}
if err := db.GetContext(ctx, result, sqlQuery, principalID, strings.ToLower(identifier)); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to find public key by principal and identifier")
}
key := mapToPublicKey(result)
return &key, nil
}
// Create creates a new public key.
func (s PublicKeyStore) Create(ctx context.Context, key *types.PublicKey) error {
const sqlQuery = `
INSERT INTO public_keys (
public_key_principal_id
,public_key_created
,public_key_verified
,public_key_identifier
,public_key_usage
,public_key_fingerprint
,public_key_content
,public_key_comment
,public_key_type
) values (
:public_key_principal_id
,:public_key_created
,:public_key_verified
,:public_key_identifier
,:public_key_usage
,:public_key_fingerprint
,:public_key_content
,:public_key_comment
,:public_key_type
) RETURNING public_key_id`
db := dbtx.GetAccessor(ctx, s.db)
dbKey := mapToInternalPublicKey(key)
query, arg, err := db.BindNamed(sqlQuery, &dbKey)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to bind public key object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&dbKey.ID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Insert public key query failed")
}
key.ID = dbKey.ID
return nil
}
// DeleteByIdentifier deletes a public key.
func (s PublicKeyStore) DeleteByIdentifier(ctx context.Context, principalID int64, identifier string) error {
const sqlQuery = `DELETE FROM public_keys WHERE public_key_principal_id = $1 and LOWER(public_key_identifier) = $2`
db := dbtx.GetAccessor(ctx, s.db)
result, err := db.ExecContext(ctx, sqlQuery, principalID, strings.ToLower(identifier))
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Delete public key query failed")
}
count, err := result.RowsAffected()
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "RowsAffected after delete of public key failed")
}
if count == 0 {
return errors.NotFound("Key not found")
}
return nil
}
// MarkAsVerified updates the public key to mark it as verified.
func (s PublicKeyStore) MarkAsVerified(ctx context.Context, id int64, verified int64) error {
const sqlQuery = `
UPDATE public_keys
SET public_key_verified = $1
WHERE public_key_id = $2`
if _, err := dbtx.GetAccessor(ctx, s.db).ExecContext(ctx, sqlQuery, verified, id); err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to mark public key as varified")
}
return nil
}
func (s PublicKeyStore) Count(
ctx context.Context,
principalID int64,
filter *types.PublicKeyFilter,
) (int, error) {
stmt := database.Builder.
Select("count(*)").
From("public_keys").
Where("public_key_principal_id = ?", principalID)
stmt = s.applyQueryFilter(stmt, filter)
sql, args, err := stmt.ToSql()
if err != nil {
return 0, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
var count int
if err := db.QueryRowContext(ctx, sql, args...).Scan(&count); err != nil {
return 0, database.ProcessSQLErrorf(ctx, err, "failed to execute count public keys query")
}
return count, nil
}
// List returns the public keys for the principal.
func (s PublicKeyStore) List(
ctx context.Context,
principalID int64,
filter *types.PublicKeyFilter,
) ([]types.PublicKey, error) {
stmt := database.Builder.
Select(publicKeyColumns).
From("public_keys").
Where("public_key_principal_id = ?", principalID)
stmt = s.applyQueryFilter(stmt, filter)
stmt = s.applySortFilter(stmt, filter)
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
keys := make([]publicKey, 0)
if err = db.SelectContext(ctx, &keys, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to execute list public keys query")
}
return mapToPublicKeys(keys), nil
}
// ListByFingerprint returns public keys given a fingerprint and key usage.
func (s PublicKeyStore) ListByFingerprint(
ctx context.Context,
fingerprint string,
) ([]types.PublicKey, error) {
stmt := database.Builder.
Select(publicKeyColumns).
From("public_keys").
Where("public_key_fingerprint = ?", fingerprint).
OrderBy("public_key_created ASC")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
keys := make([]publicKey, 0)
if err = db.SelectContext(ctx, &keys, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to execute public keys by fingerprint query")
}
return mapToPublicKeys(keys), nil
}
func (PublicKeyStore) applyQueryFilter(
stmt squirrel.SelectBuilder,
filter *types.PublicKeyFilter,
) squirrel.SelectBuilder {
if filter.Query != "" {
stmt = stmt.Where("LOWER(public_key_identifier) LIKE ?",
fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query)))
}
return stmt
}
func (PublicKeyStore) applySortFilter(
stmt squirrel.SelectBuilder,
filter *types.PublicKeyFilter,
) squirrel.SelectBuilder {
stmt = stmt.Limit(database.Limit(filter.Size))
stmt = stmt.Offset(database.Offset(filter.Page, filter.Size))
order := filter.Order
if order == enum.OrderDefault {
order = enum.OrderAsc
}
switch filter.Sort {
case enum.PublicKeySortIdentifier:
stmt = stmt.OrderBy("public_key_identifier " + order.String())
case enum.PublicKeySortCreated:
stmt = stmt.OrderBy("public_key_created " + order.String())
}
return stmt
}
func mapToInternalPublicKey(in *types.PublicKey) publicKey {
return publicKey{
ID: in.ID,
PrincipalID: in.PrincipalID,
Created: in.Created,
Verified: null.IntFromPtr(in.Verified),
Identifier: in.Identifier,
Usage: string(in.Usage),
Fingerprint: in.Fingerprint,
Content: in.Content,
Comment: in.Comment,
Type: in.Type,
}
}
func mapToPublicKey(in *publicKey) types.PublicKey {
return types.PublicKey{
ID: in.ID,
PrincipalID: in.PrincipalID,
Created: in.Created,
Verified: in.Verified.Ptr(),
Identifier: in.Identifier,
Usage: enum.PublicKeyUsage(in.Usage),
Fingerprint: in.Fingerprint,
Content: in.Content,
Comment: in.Comment,
Type: in.Type,
}
}
func mapToPublicKeys(
keys []publicKey,
) []types.PublicKey {
res := make([]types.PublicKey, len(keys))
for i := 0; i < len(keys); i++ {
res[i] = mapToPublicKey(&keys[i])
}
return res
}

View File

@ -59,6 +59,7 @@ var WireSet = wire.NewSet(
ProvideTemplateStore,
ProvideTriggerStore,
ProvidePluginStore,
ProvidePublicKeyStore,
)
// migrator is helper function to set up the database by performing automated
@ -253,3 +254,8 @@ func ProvideSettingsStore(db *sqlx.DB) store.SettingsStore {
func ProvidePublicAccessStore(db *sqlx.DB) store.PublicAccessStore {
return NewPublicAccessStore(db)
}
// ProvidePublicKeyStore provides a public key store.
func ProvidePublicKeyStore(db *sqlx.DB) store.PublicKeyStore {
return NewPublicKeyStore(db)
}

View File

@ -122,7 +122,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
principalUIDTransformation := store.ProvidePrincipalUIDTransformation()
principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation)
tokenStore := database.ProvideTokenStore(db)
controller := user.ProvideController(transactor, principalUID, authorizer, principalStore, tokenStore, membershipStore)
publicKeyStore := database.ProvidePublicKeyStore(db)
controller := user.ProvideController(transactor, principalUID, authorizer, principalStore, tokenStore, membershipStore, publicKeyStore)
serviceController := service.NewController(principalUID, authorizer, principalStore)
bootstrapBootstrap := bootstrap.ProvideBootstrap(config, controller, serviceController)
authenticator := authn.ProvideAuthenticator(config, principalStore, tokenStore)

12
go.mod
View File

@ -23,6 +23,7 @@ require (
github.com/drone/spec v0.0.0-20230919004456-7455b8913ff5
github.com/fatih/color v1.16.0
github.com/gabriel-vasile/mimetype v1.4.3
github.com/gliderlabs/ssh v0.3.7
github.com/go-chi/chi v1.5.4
github.com/go-chi/cors v1.2.1
github.com/go-redis/redis/v8 v8.11.5
@ -57,12 +58,12 @@ require (
github.com/zricethezav/gitleaks/v8 v8.18.2
go.starlark.net v0.0.0-20231121155337-90ade8b19d09
go.uber.org/multierr v1.8.0
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.17.0
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
golang.org/x/oauth2 v0.10.0
golang.org/x/sync v0.3.0
golang.org/x/term v0.13.0
golang.org/x/text v0.13.0
golang.org/x/term v0.15.0
golang.org/x/text v0.14.0
google.golang.org/api v0.132.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/mail.v2 v2.3.1
@ -76,6 +77,7 @@ require (
dario.cat/mergo v1.0.0 // indirect
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e // indirect
github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/antonmedv/expr v1.15.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@ -165,14 +167,14 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/swaggest/jsonschema-go v0.3.40 // indirect
github.com/swaggest/jsonschema-go v0.3.40
github.com/swaggest/refl v1.1.0 // indirect
github.com/vearutop/statigz v1.4.0 // indirect
github.com/yuin/goldmark v1.4.13
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

31
go.sum
View File

@ -81,6 +81,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antonmedv/expr v1.15.2 h1:afFXpDWIC2n3bF+kTZE1JvFo+c34uaM3sTqh8z0xfdU=
github.com/antonmedv/expr v1.15.2/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
@ -194,10 +196,6 @@ github.com/drone/go-convert v0.0.0-20230919093251-7104c3bcc635 h1:qQX+U2iEm4X2Fc
github.com/drone/go-convert v0.0.0-20230919093251-7104c3bcc635/go.mod h1:PyCDcuAhGF6W0VJ6qMmlM47dsSyGv/zDiMqeJxMFuGM=
github.com/drone/go-generate v0.0.0-20230920014042-6085ee5c9522 h1:i3EfRpr/eYifK9w0ninT3xHAthkS4NTQjLX0/zDIsy4=
github.com/drone/go-generate v0.0.0-20230920014042-6085ee5c9522/go.mod h1:eTfy716efMJgVvk/ZkRvitaXY2UuytfqDjxclFMeLdQ=
github.com/drone/go-scm v1.31.2 h1:6hZxf0aETV17830fMCPrgcA4y8j/8Gdfy0xEdInUeqQ=
github.com/drone/go-scm v1.31.2/go.mod h1:DFIJJjhMj0TSXPz+0ni4nyZ9gtTtC40Vh/TGRugtyWw=
github.com/drone/go-scm v1.36.0 h1:5d9lJVoXGJvLqG2CFwLcZvMMiZPNxCrmHbxRVzu19qM=
github.com/drone/go-scm v1.36.0/go.mod h1:DFIJJjhMj0TSXPz+0ni4nyZ9gtTtC40Vh/TGRugtyWw=
github.com/drone/go-scm v1.37.1 h1:U42+3JRFvmBJXZnKqphF377h2mT9Pe+Oin/lDD1fnJ8=
github.com/drone/go-scm v1.37.1/go.mod h1:DFIJJjhMj0TSXPz+0ni4nyZ9gtTtC40Vh/TGRugtyWw=
github.com/drone/runner-go v1.12.0 h1:zUjDj9ylsJ4n4Mvy4znddq/Z4EBzcUXzTltpzokKtgs=
@ -236,6 +234,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gitleaks/go-gitdiff v0.9.0 h1:SHAU2l0ZBEo8g82EeFewhVy81sb7JCxW76oSPtR/Nqg=
github.com/gitleaks/go-gitdiff v0.9.0/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@ -249,7 +249,6 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
@ -299,7 +298,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -509,19 +507,16 @@ github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXL
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@ -608,7 +603,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
@ -616,7 +610,6 @@ github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ=
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4/go.mod h1:cojhOHk1gbMeklOyDP2oKKLftefXoJreOQGOrXk+Z38=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
@ -878,8 +871,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1078,14 +1071,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1097,8 +1090,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

56
types/enum/public_key.go Normal file
View File

@ -0,0 +1,56 @@
// Copyright 2023 Harness, Inc.
//
// 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 enum
// PublicKeyUsage represents usage type of public key.
type PublicKeyUsage string
// PublicKeyUsage enumeration.
const (
PublicKeyUsageAuth PublicKeyUsage = "auth"
PublicKeyUsageSign PublicKeyUsage = "sign"
)
var publicKeyTypes = sortEnum([]PublicKeyUsage{
PublicKeyUsageAuth,
})
func (PublicKeyUsage) Enum() []interface{} { return toInterfaceSlice(publicKeyTypes) }
func (s PublicKeyUsage) Sanitize() (PublicKeyUsage, bool) {
return Sanitize(s, GetAllPublicKeyUsages)
}
func GetAllPublicKeyUsages() ([]PublicKeyUsage, PublicKeyUsage) {
return publicKeyTypes, PublicKeyUsageAuth
}
// PublicKeySort is used to specify sorting of public keys.
type PublicKeySort string
// PublicKeySort enumeration.
const (
PublicKeySortCreated PublicKeySort = "created"
PublicKeySortIdentifier PublicKeySort = "identifier"
)
var publicKeySorts = sortEnum([]PublicKeySort{
PublicKeySortCreated,
PublicKeySortIdentifier,
})
func (PublicKeySort) Enum() []interface{} { return toInterfaceSlice(publicKeySorts) }
func (s PublicKeySort) Sanitize() (PublicKeySort, bool) { return Sanitize(s, GetAllPublicKeySorts) }
func GetAllPublicKeySorts() ([]PublicKeySort, PublicKeySort) {
return publicKeySorts, PublicKeySortCreated
}

36
types/public_key.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2023 Harness, Inc.
//
// 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 types
import "github.com/harness/gitness/types/enum"
type PublicKey struct {
ID int64 `json:"-"` // frontend doesn't need it
PrincipalID int64 `json:"-"` // API always returns keys for the same user
Created int64 `json:"created"`
Verified *int64 `json:"verified"`
Identifier string `json:"identifier"`
Usage enum.PublicKeyUsage `json:"usage"`
Fingerprint string `json:"fingerprint"`
Content string `json:"-"`
Comment string `json:"comment"`
Type string `json:"type"`
}
type PublicKeyFilter struct {
ListQueryFilter
Sort enum.PublicKeySort
Order enum.Order
}