diff --git a/api/authtokens/authtoken.gen.go b/api/authtokens/authtoken.gen.go index ef395acab..8c103d995 100644 --- a/api/authtokens/authtoken.gen.go +++ b/api/authtokens/authtoken.gen.go @@ -2,12 +2,21 @@ package authtokens import ( + "encoding/json" + "strings" "time" + "github.com/fatih/structs" + + "github.com/hashicorp/watchtower/api" "github.com/hashicorp/watchtower/api/info" + "github.com/hashicorp/watchtower/api/internal/strutil" ) type AuthToken struct { + Client *api.Client `json:"-"` + + defaultFields []string // The ID of the AuthToken // Output only. @@ -39,3 +48,36 @@ type AuthToken struct { // Output only. ExpirationTime time.Time `json:"expiration_time,omitempty"` } + +func (s *AuthToken) SetDefault(key string) { + lowerKey := strings.ToLower(key) + validMap := map[string]string{"approximatelastusedtime": "approximate_last_used_time", "authmethodid": "auth_method_id", "createdtime": "created_time", "expirationtime": "expiration_time", "id": "id", "scope": "scope", "token": "token", "updatedtime": "updated_time", "userid": "user_id"} + for k, v := range validMap { + if k == lowerKey || v == lowerKey { + s.defaultFields = strutil.AppendIfMissing(s.defaultFields, v) + return + } + } +} + +func (s *AuthToken) UnsetDefault(key string) { + lowerKey := strings.ToLower(key) + validMap := map[string]string{"approximatelastusedtime": "approximate_last_used_time", "authmethodid": "auth_method_id", "createdtime": "created_time", "expirationtime": "expiration_time", "id": "id", "scope": "scope", "token": "token", "updatedtime": "updated_time", "userid": "user_id"} + for k, v := range validMap { + if k == lowerKey || v == lowerKey { + s.defaultFields = strutil.StrListDelete(s.defaultFields, v) + return + } + } +} + +func (s AuthToken) MarshalJSON() ([]byte, error) { + m := structs.Map(s) + if m == nil { + m = make(map[string]interface{}) + } + for _, k := range s.defaultFields { + m[k] = nil + } + return json.Marshal(m) +} diff --git a/api/authtokens/authtoken_test.go b/api/authtokens/authtoken_test.go new file mode 100644 index 000000000..97c07ff7e --- /dev/null +++ b/api/authtokens/authtoken_test.go @@ -0,0 +1,116 @@ +package authtokens_test + +/* +func TestAuthTokens_List(t *testing.T) { + assert := assert.New(t) + tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true}) + defer tc.Shutdown() + + client := tc.Client() + org := &scopes.Org{ + Client: client, + } + ctx := context.Background() + + atl, apiErr, err := org.ListAuthTokens(ctx) + assert.NoError(err) + assert.Nil(apiErr) + assert.Empty(atl) + + var expected []*authtokens.AuthToken + + at, apiErr, err := org.Authenticate(ctx, "am_1234567890", "name", "pw") + assert.NoError(err) + assert.Nil(apiErr) + expected = append(expected, at) + + atl, apiErr, err = org.ListAuthTokens(ctx) + assert.NoError(err) + assert.Nil(apiErr) + assert.ElementsMatch(comparableSlice(expected), comparableSlice(atl)) + + for i := 1; i < 10; i++ { + at, apiErr, err = org.Authenticate(ctx, "am_1234567890", "name", "pw") + assert.NoError(err) + assert.Nil(apiErr) + expected = append(expected, at) + } + atl, apiErr, err = org.ListAuthTokens(ctx) + require.NoError(t, err) + assert.Nil(apiErr) + assert.ElementsMatch(comparableSlice(expected), comparableSlice(atl)) +} + +func comparableResource(i *authtokens.AuthToken) authtokens.AuthToken { + return authtokens.AuthToken{ + Id: i.Id, + UserId: i.UserId, + AuthMethodId: i.AuthMethodId, + CreatedTime: i.CreatedTime, + UpdatedTime: i.UpdatedTime, + ApproximateLastUsedTime: i.ApproximateLastUsedTime, + ExpirationTime: i.ExpirationTime, + } +} + +func comparableSlice(in []*authtokens.AuthToken) []authtokens.AuthToken { + var filtered []authtokens.AuthToken + for _, i := range in { + filtered = append(filtered, comparableResource(i)) + } + sort.Slice(filtered, func(i, j int) bool { + return filtered[i].Id < filtered[j].Id + }) + return filtered +} + +func TestAuthToken_Crud(t *testing.T) { + tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true}) + defer tc.Shutdown() + + client := tc.Client() + org := &scopes.Org{ + Client: client, + } + + want, apiErr, err := org.Authenticate(tc.Context(), "am_1234567890", "name", "pw") + + at, apiErr, err := org.ReadAuthToken(tc.Context(), &authtokens.AuthToken{Id: want.Id}) + require.NoError(t, err) + assert.Nil(t, apiErr) + at.Token = "" + assert.EqualValues(t, comparableResource(want), comparableResource(at)) + + existed, _, err := org.DeleteAuthToken(tc.Context(), at) + require.NoError(t, err) + assert.Nil(t, apiErr) + assert.True(t, existed, "Expected existing user when deleted, but it wasn't.") + + existed, apiErr, err = org.DeleteAuthToken(tc.Context(), at) + require.NoError(t, err) + assert.Nil(t, apiErr) + assert.False(t, existed, "Expected user to not exist when deleted, but it did.") +} + +func TestAuthToken_Errors(t *testing.T) { + assert := assert.New(t) + tc := controller.NewTestController(t, &controller.TestControllerOpts{DisableAuthorizationFailures: true}) + defer tc.Shutdown() + ctx := tc.Context() + + client := tc.Client() + org := &scopes.Org{ + Client: client, + } + + _, apiErr, err := org.ReadAuthToken(ctx, &authtokens.AuthToken{Id: authtoken.AuthTokenPrefix + "_doesntexis"}) + assert.NoError(err) + assert.NotNil(apiErr) + assert.EqualValues(apiErr.Status, http.StatusNotFound) + + _, apiErr, err = org.ReadAuthToken(ctx, &authtokens.AuthToken{Id: "invalid id"}) + assert.NoError(err) + assert.NotNil(apiErr) + assert.EqualValues(apiErr.Status, http.StatusBadRequest) +} +*/ diff --git a/api/authtokens/delete.gen.go b/api/authtokens/delete.gen.go new file mode 100644 index 000000000..eb93159d9 --- /dev/null +++ b/api/authtokens/delete.gen.go @@ -0,0 +1,49 @@ +// Code generated by "make api"; DO NOT EDIT. +package authtokens + +import ( + "context" + "fmt" + + "github.com/hashicorp/watchtower/api" +) + +// DeleteAuthToken returns true iff the AuthToken existed when the delete attempt was made. +func (s AuthToken) DeleteAuthToken(ctx context.Context, id string) (bool, *api.Error, error) { + if id == "" { + return false, nil, fmt.Errorf("empty id provided to DeleteAuthToken request") + } + + if s.Client == nil { + return false, nil, fmt.Errorf("nil client in DeleteAuthToken request") + } + + var opts []api.Option + if s.Scope.Id != "" { + // If it's explicitly set here, override anything that might be in the + // client + opts = append(opts, api.WithScopeId(s.Scope.Id)) + } + + req, err := s.Client.NewRequest(ctx, "DELETE", fmt.Sprintf("%s/%s", "auth-tokens", id), nil, opts...) + if err != nil { + return false, nil, fmt.Errorf("error creating DeleteAuthToken request: %w", err) + } + + resp, err := s.Client.Do(req) + if err != nil { + return false, nil, fmt.Errorf("error performing client request during DeleteAuthToken call: %w", err) + } + + type deleteResponse struct { + Existed bool + } + target := &deleteResponse{} + + apiErr, err := resp.Decode(target) + if err != nil { + return false, nil, fmt.Errorf("error decoding DeleteAuthToken repsonse: %w", err) + } + + return target.Existed, apiErr, nil +} diff --git a/api/authtokens/list.gen.go b/api/authtokens/list.gen.go new file mode 100644 index 000000000..b27b51d80 --- /dev/null +++ b/api/authtokens/list.gen.go @@ -0,0 +1,50 @@ +// Code generated by "make api"; DO NOT EDIT. +package authtokens + +import ( + "context" + "fmt" + + "github.com/hashicorp/watchtower/api" +) + +func (s AuthToken) ListAuthTokens(ctx context.Context) ([]*AuthToken, *api.Error, error) { + if s.Client == nil { + return nil, nil, fmt.Errorf("nil client in ListAuthToken request") + } + + var opts []api.Option + if s.Scope.Id != "" { + // If it's explicitly set here, override anything that might be in the + // client + opts = append(opts, api.WithScopeId(s.Scope.Id)) + } + + req, err := s.Client.NewRequest(ctx, "GET", "auth-tokens", nil, opts...) + if err != nil { + return nil, nil, fmt.Errorf("error creating ListAuthTokens request: %w", err) + } + + resp, err := s.Client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("error performing client request during ListAuthTokens call: %w", err) + } + + type listResponse struct { + Items []*AuthToken + } + target := &listResponse{} + + apiErr, err := resp.Decode(target) + if err != nil { + return nil, nil, fmt.Errorf("error decoding ListAuthTokens response: %w", err) + } + + for _, t := range target.Items { + + t.Client = s.Client + + } + + return target.Items, apiErr, nil +} diff --git a/api/authtokens/read.gen.go b/api/authtokens/read.gen.go new file mode 100644 index 000000000..e7f1222de --- /dev/null +++ b/api/authtokens/read.gen.go @@ -0,0 +1,46 @@ +// Code generated by "make api"; DO NOT EDIT. +package authtokens + +import ( + "context" + "fmt" + + "github.com/hashicorp/watchtower/api" +) + +func (s AuthToken) ReadAuthToken(ctx context.Context, id string) (*AuthToken, *api.Error, error) { + if id == "" { + return nil, nil, fmt.Errorf("empty ID value passed into ReadAuthToken request") + } + + if s.Client == nil { + return nil, nil, fmt.Errorf("nil client in ReadAuthToken request") + } + + var opts []api.Option + if s.Scope.Id != "" { + // If it's explicitly set here, override anything that might be in the + // client + opts = append(opts, api.WithScopeId(s.Scope.Id)) + } + + req, err := s.Client.NewRequest(ctx, "GET", fmt.Sprintf("%s/%s", "auth-tokens", id), nil, opts...) + if err != nil { + return nil, nil, fmt.Errorf("error creating ReadAuthToken request: %w", err) + } + + resp, err := s.Client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("error performing client request during ReadAuthToken call: %w", err) + } + + target := new(AuthToken) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, nil, fmt.Errorf("error decoding ReadAuthToken repsonse: %w", err) + } + + target.Client = s.Client + + return target, apiErr, nil +} diff --git a/api/internal/genapi/delete.go b/api/internal/genapi/delete.go index ae6cd2448..08442e27c 100644 --- a/api/internal/genapi/delete.go +++ b/api/internal/genapi/delete.go @@ -23,6 +23,13 @@ var deleteFuncs = map[string][]*deleteInfo{ "scopes", }, }, + "authtokens": { + { + "AuthToken", + "AuthToken", + "auth-tokens", + }, + }, "groups": { { "Group", diff --git a/api/internal/genapi/input.go b/api/internal/genapi/input.go index e22309af6..1f7fa2436 100644 --- a/api/internal/genapi/input.go +++ b/api/internal/genapi/input.go @@ -63,12 +63,11 @@ var inputStructs = []*structInfo{ outPkg: "hosts", }, { - inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/authtokens/authtoken.pb.go", - inName: "AuthToken", - outFile: os.Getenv("GEN_BASEPATH") + "/api/authtokens/authtoken.gen.go", - outName: "AuthToken", - outPkg: "authtokens", - outputOnly: true, + inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/authtokens/authtoken.pb.go", + inName: "AuthToken", + outFile: os.Getenv("GEN_BASEPATH") + "/api/authtokens/authtoken.gen.go", + outName: "AuthToken", + outPkg: "authtokens", }, { inFile: os.Getenv("GEN_BASEPATH") + "/internal/gen/controller/api/resources/scopes/scope.pb.go", diff --git a/api/internal/genapi/list.go b/api/internal/genapi/list.go index 98f05d1c0..e28aada22 100644 --- a/api/internal/genapi/list.go +++ b/api/internal/genapi/list.go @@ -23,6 +23,13 @@ var listFuncs = map[string][]*listInfo{ path: "scopes", }, }, + "authtokens": { + { + baseType: "AuthToken", + targetType: "AuthToken", + path: "auth-tokens", + }, + }, "groups": { { baseType: "Group", diff --git a/api/internal/genapi/read.go b/api/internal/genapi/read.go index b3f0d44e1..94c1cfa7f 100644 --- a/api/internal/genapi/read.go +++ b/api/internal/genapi/read.go @@ -23,6 +23,13 @@ var readFuncs = map[string][]*readInfo{ path: "scopes", }, }, + "authtokens": { + { + baseType: "AuthToken", + targetType: "AuthToken", + path: "auth-tokens", + }, + }, "groups": { { baseType: "Group", diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index a90bea6ac..46623e215 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -290,7 +290,8 @@ func TestAuthTokenAuthenticator(t *testing.T) { return iamRepo, nil } - at := authtoken.TestAuthToken(t, conn, wrapper) + o, _ := iam.TestScopes(t, conn) + at := authtoken.TestAuthToken(t, conn, wrapper, o.GetPublicId()) tokValue := at.GetPublicId() + "_" + at.GetToken() jsCookieVal, httpCookieVal := tokValue[:len(tokValue)/2], tokValue[len(tokValue)/2:] diff --git a/internal/authtoken/authtoken.go b/internal/authtoken/authtoken.go index f97d1ee77..6abf1303c 100644 --- a/internal/authtoken/authtoken.go +++ b/internal/authtoken/authtoken.go @@ -12,6 +12,27 @@ import ( "google.golang.org/protobuf/proto" ) +// writableAuthToken is used for auth token writes. Since gorm relies on the TableName interface this allows +// us to use a base table for writes and a view for reads. +type writableAuthToken struct { + *store.AuthToken + tableName string `gorm:"-"` +} + +func (s *writableAuthToken) clone() *writableAuthToken { + cp := proto.Clone(s.AuthToken) + return &writableAuthToken{ + AuthToken: cp.(*store.AuthToken), + } +} + +func (s *writableAuthToken) toAuthToken() *AuthToken { + cp := proto.Clone(s.AuthToken) + return &AuthToken{ + AuthToken: cp.(*store.AuthToken), + } +} + // A AuthToken contains auth tokens. It is owned by a scope. type AuthToken struct { *store.AuthToken @@ -25,8 +46,15 @@ func (s *AuthToken) clone() *AuthToken { } } +func (s *AuthToken) toWritableAuthToken() *writableAuthToken { + cp := proto.Clone(s.AuthToken) + return &writableAuthToken{ + AuthToken: cp.(*store.AuthToken), + } +} + // encrypt the entry's data using the provided cipher (wrapping.Wrapper) -func (s *AuthToken) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { +func (s *writableAuthToken) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { // structwrapping doesn't support embedding, so we'll pass in the store.Entry directly if err := structwrapping.WrapStruct(ctx, cipher, s.AuthToken, nil); err != nil { return fmt.Errorf("error encrypting auth token: %w", err) diff --git a/internal/authtoken/authtoken_test.go b/internal/authtoken/authtoken_test.go index fad118a0f..e99d48571 100644 --- a/internal/authtoken/authtoken_test.go +++ b/internal/authtoken/authtoken_test.go @@ -94,12 +94,14 @@ func TestAuthToken_DbUpdate(t *testing.T) { assert := assert.New(t) w := db.New(conn) - authTok := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + authTok := TestAuthToken(t, conn, wrapper, org.GetPublicId()) proto.Merge(authTok.AuthToken, tt.args.authTok) - err := authTok.encrypt(context.Background(), wrapper) + wAuthToken := authTok.toWritableAuthToken() + err := wAuthToken.encrypt(context.Background(), wrapper) require.NoError(t, err) - cnt, err := w.Update(context.Background(), authTok, tt.args.fieldMask, tt.args.nullMask) + cnt, err := w.Update(context.Background(), wAuthToken, tt.args.fieldMask, tt.args.nullMask) if tt.wantErr { t.Logf("Got error :%v", err) assert.Error(err) @@ -119,7 +121,7 @@ func TestAuthToken_DbCreate(t *testing.T) { u := iam.TestUser(t, conn, org.GetPublicId()) amId := setupAuthMethod(t, conn, org.GetPublicId()) acct := setupAuthAccount(t, conn, org.GetPublicId(), amId, u.GetPublicId()) - createdAuthToken := TestAuthToken(t, conn, wrapper) + createdAuthToken := TestAuthToken(t, conn, wrapper, org.GetPublicId()) testAuthTokenId := func() string { id, err := newAuthTokenId() @@ -155,7 +157,7 @@ func TestAuthToken_DbCreate(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - at := &AuthToken{AuthToken: tt.in} + at := &writableAuthToken{AuthToken: tt.in} err := at.encrypt(context.Background(), wrapper) require.NoError(t, err) err = db.New(conn).Create(context.Background(), at) @@ -177,22 +179,23 @@ func TestAuthToken_DbDelete(t *testing.T) { } wrapper := db.TestWrapper(t) - existingAuthTok := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + existingAuthTok := TestAuthToken(t, conn, wrapper, org.GetPublicId()) var tests = []struct { name string - at *AuthToken + at *writableAuthToken wantError bool wantCnt int }{ { name: "basic", - at: &AuthToken{AuthToken: &store.AuthToken{PublicId: existingAuthTok.GetPublicId()}}, + at: &writableAuthToken{AuthToken: &store.AuthToken{PublicId: existingAuthTok.GetPublicId()}}, wantCnt: 1, }, { name: "delete-nothing", - at: &AuthToken{AuthToken: &store.AuthToken{PublicId: testAuthTokenId()}}, + at: &writableAuthToken{AuthToken: &store.AuthToken{PublicId: testAuthTokenId()}}, wantCnt: 0, }, { @@ -203,7 +206,7 @@ func TestAuthToken_DbDelete(t *testing.T) { }, { name: "delete-no-public-id", - at: &AuthToken{AuthToken: &store.AuthToken{}}, + at: &writableAuthToken{AuthToken: &store.AuthToken{}}, wantCnt: 0, wantError: true, }, diff --git a/internal/authtoken/gorm.go b/internal/authtoken/gorm.go index 4b4343b4b..a8ef1db3b 100644 --- a/internal/authtoken/gorm.go +++ b/internal/authtoken/gorm.go @@ -1,6 +1,7 @@ package authtoken -const defaultAuthTokenTableName = "auth_token" +const defaultAuthTokenTableName = "auth_token_account" +const defaultWritableAuthTokenTableName = "auth_token" // TableName returns the table name for the auth token. func (s *AuthToken) TableName() string { @@ -20,3 +21,22 @@ func (s *AuthToken) SetTableName(n string) { s.tableName = n } } + +// TableName returns the table name for the auth token. +func (s *writableAuthToken) TableName() string { + if s.tableName != "" { + return s.tableName + } + return defaultWritableAuthTokenTableName +} + +// SetTableName sets the table name. If the caller attempts to +// set the name to "" the name will be reset to the default name. +func (s *writableAuthToken) SetTableName(n string) { + switch n { + case "": + s.tableName = defaultWritableAuthTokenTableName + default: + s.tableName = n + } +} \ No newline at end of file diff --git a/internal/authtoken/options.go b/internal/authtoken/options.go index e1e83ad12..a3bdda823 100644 --- a/internal/authtoken/options.go +++ b/internal/authtoken/options.go @@ -15,6 +15,7 @@ type Option func(*options) // options = how options are represented type options struct { withTokenValue bool + withLimit int } func getDefaultOptions() options { @@ -28,3 +29,12 @@ func withTokenValue() Option { o.withTokenValue = true } } + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are returned. +// If WithLimit == 0, then default limits are used for results. +func WithLimit(limit int) Option { + return func(o *options) { + o.withLimit = limit + } +} diff --git a/internal/authtoken/repository.go b/internal/authtoken/repository.go index fccb0c34d..1f8b526f1 100644 --- a/internal/authtoken/repository.go +++ b/internal/authtoken/repository.go @@ -30,11 +30,13 @@ type Repository struct { reader db.Reader writer db.Writer wrapper wrapping.Wrapper + // defaultLimit provides a default for limiting the number of results returned from the repo + defaultLimit int } // NewRepository creates a new Repository. The returned repository is not safe for concurrent go // routines to access it. -func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Repository, error) { +func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) { switch { case r == nil: return nil, fmt.Errorf("db.Reader: auth token: %w", db.ErrNilParameter) @@ -44,10 +46,16 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Reposit return nil, fmt.Errorf("wrapping.Wrapper: auth token: %w", db.ErrNilParameter) } + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the watchtower defaults should be used. + opts.withLimit = db.DefaultLimit + } return &Repository{ - reader: r, - writer: w, - wrapper: wrapper, + reader: r, + writer: w, + wrapper: wrapper, + defaultLimit: opts.withLimit, }, nil } @@ -84,7 +92,7 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut } at.ExpirationTime = ×tamp.Timestamp{Timestamp: expiration} - var newAuthToken *AuthToken + var newAuthToken *writableAuthToken _, err = r.writer.DoTx( ctx, db.StdRetryCnt, @@ -103,8 +111,7 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut at.IamUserId = acct.GetIamUserId() metadata := newAuthTokenMetadata(at, oplog.OpType_OP_TYPE_CREATE) - - newAuthToken = at.clone() + newAuthToken = at.toWritableAuthToken() if err := newAuthToken.encrypt(ctx, r.wrapper); err != nil { return err } @@ -120,7 +127,7 @@ func (r *Repository) CreateAuthToken(ctx context.Context, withIamUserId, withAut if err != nil { return nil, fmt.Errorf("create: auth token: %v: %w", at, err) } - return newAuthToken, nil + return newAuthToken.toAuthToken(), nil } // LookupAuthToken returns the AuthToken for the provided id. Returns nil, nil if no AuthToken is found for id. @@ -134,8 +141,6 @@ func (r *Repository) LookupAuthToken(ctx context.Context, id string, opt ...Opti at := allocAuthToken() at.PublicId = id - // Aggregate the fields across auth token and auth accounts by using this view instead of issuing 2 db lookups. - at.SetTableName("auth_token_account") if err := r.reader.LookupByPublicId(ctx, at); err != nil { if errors.Is(err, db.ErrRecordNotFound) { return nil, nil @@ -199,7 +204,7 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { metadata := newAuthTokenMetadata(retAT, oplog.OpType_OP_TYPE_DELETE) - delAt := retAT.clone() + delAt := retAT.toWritableAuthToken() if _, err := w.Delete(ctx, delAt, db.WithOplog(r.wrapper, metadata)); err != nil { return fmt.Errorf("validate token: delete auth token: %w", err) } @@ -224,10 +229,10 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { + metadata := newAuthTokenMetadata(retAT, oplog.OpType_OP_TYPE_UPDATE) + at := retAT.toWritableAuthToken() // Setting the ApproximateLastAccessTime to null through using the null mask allows a defined db's // trigger to set ApproximateLastAccessTime to the commit timestamp. - at := retAT.clone() - metadata := newAuthTokenMetadata(retAT, oplog.OpType_OP_TYPE_UPDATE) rowsUpdated, err := w.Update( ctx, at, @@ -249,7 +254,27 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt .. return retAT, nil } -// TODO(ICU-344): Add ListAuthTokens +// ListAuthTokens in an org and supports the WithLimit option. +func (r *Repository) ListAuthTokens(ctx context.Context, withOrgId string, opt ...Option) ([]*AuthToken, error) { + if withOrgId == "" { + return nil, fmt.Errorf("list users: missing org id %w", db.ErrInvalidParameter) + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var authTokens []*AuthToken + if err := r.reader.SearchWhere(ctx, &authTokens, "auth_account_id in (select public_id from auth_account where scope_id = ?)", []interface{}{withOrgId}, db.WithLimit(limit)); err != nil { + return nil, fmt.Errorf("list users: %w", err) + } + for _, at := range authTokens { + at.Token = "" + at.CtToken = nil + } + return authTokens, nil +} // DeleteAuthToken deletes the token with the provided id from the repository returning a count of the // number of records deleted. All options are ignored. @@ -276,8 +301,7 @@ func (r *Repository) DeleteAuthToken(ctx context.Context, id string, opt ...Opti db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { metadata := newAuthTokenMetadata(at, oplog.OpType_OP_TYPE_DELETE) - - deleteAT := at.clone() + deleteAT := at.toWritableAuthToken() rowsDeleted, err = w.Delete(ctx, deleteAT, db.WithOplog(r.wrapper, metadata)) if err == nil && rowsDeleted > 1 { return db.ErrMultipleRecords diff --git a/internal/authtoken/repository_test.go b/internal/authtoken/repository_test.go index fe9918abc..465030325 100644 --- a/internal/authtoken/repository_test.go +++ b/internal/authtoken/repository_test.go @@ -29,6 +29,7 @@ func TestRepository_New(t *testing.T) { r db.Reader w db.Writer wrapper wrapping.Wrapper + opts []Option } var tests = []struct { @@ -38,16 +39,32 @@ func TestRepository_New(t *testing.T) { wantIsErr error }{ { - name: "valid", + name: "valid default limit", args: args{ r: rw, w: rw, wrapper: wrapper, }, want: &Repository{ - reader: rw, - writer: rw, + reader: rw, + writer: rw, + wrapper: wrapper, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid new limit", + args: args{ + r: rw, + w: rw, wrapper: wrapper, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + wrapper: wrapper, + defaultLimit: 5, }, }, { @@ -95,7 +112,7 @@ func TestRepository_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...) if tt.wantIsErr != nil { assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err) assert.Nil(got) @@ -196,7 +213,8 @@ func TestRepository_LookupAuthToken(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - at := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) at.Token = "" at.CtToken = nil @@ -273,7 +291,8 @@ func TestRepository_ValidateToken(t *testing.T) { require.NoError(t, err) require.NotNil(t, repo) - at := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) atToken := at.GetToken() at.Token = "" at.CtToken = nil @@ -382,7 +401,8 @@ func TestRepository_ValidateToken_expired(t *testing.T) { require.NoError(t, err) require.NotNil(t, repo) - baseAT := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + baseAT := TestAuthToken(t, conn, wrapper, org.GetPublicId()) baseAT.GetAuthAccountId() aAcct := &iam.AuthAccount{AuthAccount: &iamStore.AuthAccount{PublicId: baseAT.GetAuthAccountId()}} require.NoError(t, rw.LookupByPublicId(context.Background(), aAcct)) @@ -450,7 +470,8 @@ func TestRepository_DeleteAuthToken(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) - at := TestAuthToken(t, conn, wrapper) + org, _ := iam.TestScopes(t, conn) + at := TestAuthToken(t, conn, wrapper, org.GetPublicId()) badId, err := newAuthTokenId() require.NoError(t, err) require.NotNil(t, badId) @@ -500,3 +521,61 @@ func TestRepository_DeleteAuthToken(t *testing.T) { }) } } + +func TestRepository_ListAuthTokens(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + org, _ := iam.TestScopes(t, conn) + at1 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + at1.Token = "" + at2 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + at2.Token = "" + at3 := TestAuthToken(t, conn, wrapper, org.GetPublicId()) + at3.Token = "" + + emptyOrg, _ := iam.TestScopes(t, conn) + + var tests = []struct { + name string + orgId string + want []*AuthToken + wantErr error + }{ + { + name: "populated", + orgId: org.GetPublicId(), + want: []*AuthToken{at1, at2, at3}, + }, + { + name: "empty", + orgId: emptyOrg.GetPublicId(), + want: []*AuthToken{}, + }, + { + name: "empty-org-id", + orgId: "", + want: nil, + wantErr: db.ErrInvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, wrapper) + require.NoError(err) + require.NotNil(repo) + + got, err := repo.ListAuthTokens(context.Background(), tt.orgId) + if tt.wantErr != nil { + assert.Truef(errors.Is(err, tt.wantErr), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + assert.ElementsMatchf(tt.want, got, "row count") + }) + } +} diff --git a/internal/authtoken/testing.go b/internal/authtoken/testing.go index 7c83d77d0..23a813a25 100644 --- a/internal/authtoken/testing.go +++ b/internal/authtoken/testing.go @@ -12,24 +12,22 @@ import ( "github.com/stretchr/testify/require" ) -func TestAuthToken(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper) *AuthToken { +func TestAuthToken(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string) *AuthToken { t.Helper() - require := require.New(t) - org, _ := iam.TestScopes(t, conn) - u := iam.TestUser(t, conn, org.GetPublicId()) - amId := setupAuthMethod(t, conn, org.GetPublicId()) + u := iam.TestUser(t, conn, scopeId) + amId := setupAuthMethod(t, conn, scopeId) // auth account is only used to join auth method to user. // We don't do anything else with the auth account in the test setup. - acct := setupAuthAccount(t, conn, org.GetPublicId(), amId, u.GetPublicId()) + acct := setupAuthAccount(t, conn, scopeId, amId, u.GetPublicId()) rw := db.New(conn) repo, err := NewRepository(rw, rw, wrapper) - require.NoError(err) + require.NoError(t, err) ctx := context.Background() at, err := repo.CreateAuthToken(ctx, u.GetPublicId(), acct.GetPublicId()) - require.NoError(err) + require.NoError(t, err) return at } diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 9932552c4..698304f77 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -106,6 +106,85 @@ ] } }, + "/v1/scopes/*/auth-tokens": { + "get": { + "summary": "Lists all AuthTokens", + "operationId": "AuthTokenService_ListAuthTokens", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.ListAuthTokensResponse" + } + } + }, + "parameters": [ + { + "name": "view", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.AuthTokenService" + ] + } + }, + "/v1/scopes/*/auth-tokens/{id}": { + "get": { + "summary": "Gets a single AuthToken", + "operationId": "AuthTokenService_GetAuthToken", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.authtokens.v1.AuthToken" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "view", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.AuthTokenService" + ] + }, + "delete": { + "summary": "Deletes an AuthTokens", + "operationId": "AuthTokenService_DeleteAuthToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.DeleteAuthTokenResponse" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.AuthTokenService" + ] + } + }, "/v1/scopes/*/groups": { "get": { "summary": "Lists all Groups", @@ -2165,6 +2244,15 @@ } } }, + "controller.api.services.v1.DeleteAuthTokenResponse": { + "type": "object", + "properties": { + "existed": { + "type": "boolean", + "format": "boolean" + } + } + }, "controller.api.services.v1.DeleteGroupResponse": { "type": "object", "properties": { @@ -2228,6 +2316,14 @@ } } }, + "controller.api.services.v1.GetAuthTokenResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.authtokens.v1.AuthToken" + } + } + }, "controller.api.services.v1.GetGroupResponse": { "type": "object", "properties": { @@ -2284,6 +2380,17 @@ } } }, + "controller.api.services.v1.ListAuthTokensResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.authtokens.v1.AuthToken" + } + } + } + }, "controller.api.services.v1.ListGroupsResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/authtokens_service.pb.go b/internal/gen/controller/api/services/authtokens_service.pb.go new file mode 100644 index 000000000..4e078fadb --- /dev/null +++ b/internal/gen/controller/api/services/authtokens_service.pb.go @@ -0,0 +1,723 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.12.3 +// source: controller/api/services/v1/authtokens_service.proto + +package services + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options" + authtokens "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authtokens" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type GetAuthTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + View string `protobuf:"bytes,2,opt,name=view,proto3" json:"view,omitempty"` +} + +func (x *GetAuthTokenRequest) Reset() { + *x = GetAuthTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAuthTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAuthTokenRequest) ProtoMessage() {} + +func (x *GetAuthTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAuthTokenRequest.ProtoReflect.Descriptor instead. +func (*GetAuthTokenRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetAuthTokenRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetAuthTokenRequest) GetView() string { + if x != nil { + return x.View + } + return "" +} + +type GetAuthTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *authtokens.AuthToken `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *GetAuthTokenResponse) Reset() { + *x = GetAuthTokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAuthTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAuthTokenResponse) ProtoMessage() {} + +func (x *GetAuthTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAuthTokenResponse.ProtoReflect.Descriptor instead. +func (*GetAuthTokenResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetAuthTokenResponse) GetItem() *authtokens.AuthToken { + if x != nil { + return x.Item + } + return nil +} + +type ListAuthTokensRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + View string `protobuf:"bytes,1,opt,name=view,proto3" json:"view,omitempty"` +} + +func (x *ListAuthTokensRequest) Reset() { + *x = ListAuthTokensRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAuthTokensRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAuthTokensRequest) ProtoMessage() {} + +func (x *ListAuthTokensRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAuthTokensRequest.ProtoReflect.Descriptor instead. +func (*ListAuthTokensRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListAuthTokensRequest) GetView() string { + if x != nil { + return x.View + } + return "" +} + +type ListAuthTokensResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*authtokens.AuthToken `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ListAuthTokensResponse) Reset() { + *x = ListAuthTokensResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAuthTokensResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAuthTokensResponse) ProtoMessage() {} + +func (x *ListAuthTokensResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAuthTokensResponse.ProtoReflect.Descriptor instead. +func (*ListAuthTokensResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAuthTokensResponse) GetItems() []*authtokens.AuthToken { + if x != nil { + return x.Items + } + return nil +} + +type DeleteAuthTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteAuthTokenRequest) Reset() { + *x = DeleteAuthTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAuthTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAuthTokenRequest) ProtoMessage() {} + +func (x *DeleteAuthTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAuthTokenRequest.ProtoReflect.Descriptor instead. +func (*DeleteAuthTokenRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{4} +} + +func (x *DeleteAuthTokenRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeleteAuthTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Existed bool `protobuf:"varint,1,opt,name=existed,proto3" json:"existed,omitempty"` +} + +func (x *DeleteAuthTokenResponse) Reset() { + *x = DeleteAuthTokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAuthTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAuthTokenResponse) ProtoMessage() {} + +func (x *DeleteAuthTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_authtokens_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAuthTokenResponse.ProtoReflect.Descriptor instead. +func (*DeleteAuthTokenResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteAuthTokenResponse) GetExisted() bool { + if x != nil { + return x.Existed + } + return false +} + +var File_controller_api_services_v1_authtokens_service_proto protoreflect.FileDescriptor + +var file_controller_api_services_v1_authtokens_service_proto_rawDesc = []byte{ + 0x0a, 0x33, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, + 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x1a, 0x2c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x73, 0x77, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x36, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x39, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x76, 0x69, 0x65, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, + 0x22, 0x5d, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, + 0x2b, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, 0x22, 0x61, 0x0a, 0x16, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, + 0x28, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x33, 0x0a, 0x17, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x32, 0xc2, + 0x04, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0xba, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x47, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, + 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x61, 0x75, + 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x19, 0x12, 0x17, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0xb2, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, + 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x92, 0x41, 0x16, 0x12, + 0x14, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x41, 0x75, 0x74, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0xbb, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x3f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x2a, 0x1d, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x2a, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, 0x17, 0x12, 0x15, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, + 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_services_v1_authtokens_service_proto_rawDescOnce sync.Once + file_controller_api_services_v1_authtokens_service_proto_rawDescData = file_controller_api_services_v1_authtokens_service_proto_rawDesc +) + +func file_controller_api_services_v1_authtokens_service_proto_rawDescGZIP() []byte { + file_controller_api_services_v1_authtokens_service_proto_rawDescOnce.Do(func() { + file_controller_api_services_v1_authtokens_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_authtokens_service_proto_rawDescData) + }) + return file_controller_api_services_v1_authtokens_service_proto_rawDescData +} + +var file_controller_api_services_v1_authtokens_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_controller_api_services_v1_authtokens_service_proto_goTypes = []interface{}{ + (*GetAuthTokenRequest)(nil), // 0: controller.api.services.v1.GetAuthTokenRequest + (*GetAuthTokenResponse)(nil), // 1: controller.api.services.v1.GetAuthTokenResponse + (*ListAuthTokensRequest)(nil), // 2: controller.api.services.v1.ListAuthTokensRequest + (*ListAuthTokensResponse)(nil), // 3: controller.api.services.v1.ListAuthTokensResponse + (*DeleteAuthTokenRequest)(nil), // 4: controller.api.services.v1.DeleteAuthTokenRequest + (*DeleteAuthTokenResponse)(nil), // 5: controller.api.services.v1.DeleteAuthTokenResponse + (*authtokens.AuthToken)(nil), // 6: controller.api.resources.authtokens.v1.AuthToken +} +var file_controller_api_services_v1_authtokens_service_proto_depIdxs = []int32{ + 6, // 0: controller.api.services.v1.GetAuthTokenResponse.item:type_name -> controller.api.resources.authtokens.v1.AuthToken + 6, // 1: controller.api.services.v1.ListAuthTokensResponse.items:type_name -> controller.api.resources.authtokens.v1.AuthToken + 0, // 2: controller.api.services.v1.AuthTokenService.GetAuthToken:input_type -> controller.api.services.v1.GetAuthTokenRequest + 2, // 3: controller.api.services.v1.AuthTokenService.ListAuthTokens:input_type -> controller.api.services.v1.ListAuthTokensRequest + 4, // 4: controller.api.services.v1.AuthTokenService.DeleteAuthToken:input_type -> controller.api.services.v1.DeleteAuthTokenRequest + 1, // 5: controller.api.services.v1.AuthTokenService.GetAuthToken:output_type -> controller.api.services.v1.GetAuthTokenResponse + 3, // 6: controller.api.services.v1.AuthTokenService.ListAuthTokens:output_type -> controller.api.services.v1.ListAuthTokensResponse + 5, // 7: controller.api.services.v1.AuthTokenService.DeleteAuthToken:output_type -> controller.api.services.v1.DeleteAuthTokenResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_controller_api_services_v1_authtokens_service_proto_init() } +func file_controller_api_services_v1_authtokens_service_proto_init() { + if File_controller_api_services_v1_authtokens_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_services_v1_authtokens_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAuthTokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_authtokens_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAuthTokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_authtokens_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAuthTokensRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_authtokens_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAuthTokensResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_authtokens_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAuthTokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_authtokens_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAuthTokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_services_v1_authtokens_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_controller_api_services_v1_authtokens_service_proto_goTypes, + DependencyIndexes: file_controller_api_services_v1_authtokens_service_proto_depIdxs, + MessageInfos: file_controller_api_services_v1_authtokens_service_proto_msgTypes, + }.Build() + File_controller_api_services_v1_authtokens_service_proto = out.File + file_controller_api_services_v1_authtokens_service_proto_rawDesc = nil + file_controller_api_services_v1_authtokens_service_proto_goTypes = nil + file_controller_api_services_v1_authtokens_service_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// AuthTokenServiceClient is the client API for AuthTokenService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type AuthTokenServiceClient interface { + // GetAuthToken returns a stored auth token if present. The provided request + // must include the org and auth token id for the auth token being retrieved. If + // any of those ids are missing, malformed or reference a non existing + // resource an error is returned. + GetAuthToken(ctx context.Context, in *GetAuthTokenRequest, opts ...grpc.CallOption) (*GetAuthTokenResponse, error) + // ListAuthTokens returns a list of stored auth tokens which exist inside the org + // referenced inside the request. The request must include the org id for + // the auth token being retrieved. If the org id is missing, malformed, or + // reference a non existing organization, an error is returned. + ListAuthTokens(ctx context.Context, in *ListAuthTokensRequest, opts ...grpc.CallOption) (*ListAuthTokensResponse, error) + // DeleteAuthToken removes a auth token from Watchtower. If the provided org or + // auth token ids are malformed or not provided an error is returned. No error is returned + // if either ids reference resources that do not exist as the response itself + // specifies if the resource existed before the DeleteAuthToken request was + // received. + DeleteAuthToken(ctx context.Context, in *DeleteAuthTokenRequest, opts ...grpc.CallOption) (*DeleteAuthTokenResponse, error) +} + +type authTokenServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthTokenServiceClient(cc grpc.ClientConnInterface) AuthTokenServiceClient { + return &authTokenServiceClient{cc} +} + +func (c *authTokenServiceClient) GetAuthToken(ctx context.Context, in *GetAuthTokenRequest, opts ...grpc.CallOption) (*GetAuthTokenResponse, error) { + out := new(GetAuthTokenResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.AuthTokenService/GetAuthToken", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authTokenServiceClient) ListAuthTokens(ctx context.Context, in *ListAuthTokensRequest, opts ...grpc.CallOption) (*ListAuthTokensResponse, error) { + out := new(ListAuthTokensResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.AuthTokenService/ListAuthTokens", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authTokenServiceClient) DeleteAuthToken(ctx context.Context, in *DeleteAuthTokenRequest, opts ...grpc.CallOption) (*DeleteAuthTokenResponse, error) { + out := new(DeleteAuthTokenResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.AuthTokenService/DeleteAuthToken", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthTokenServiceServer is the server API for AuthTokenService service. +type AuthTokenServiceServer interface { + // GetAuthToken returns a stored auth token if present. The provided request + // must include the org and auth token id for the auth token being retrieved. If + // any of those ids are missing, malformed or reference a non existing + // resource an error is returned. + GetAuthToken(context.Context, *GetAuthTokenRequest) (*GetAuthTokenResponse, error) + // ListAuthTokens returns a list of stored auth tokens which exist inside the org + // referenced inside the request. The request must include the org id for + // the auth token being retrieved. If the org id is missing, malformed, or + // reference a non existing organization, an error is returned. + ListAuthTokens(context.Context, *ListAuthTokensRequest) (*ListAuthTokensResponse, error) + // DeleteAuthToken removes a auth token from Watchtower. If the provided org or + // auth token ids are malformed or not provided an error is returned. No error is returned + // if either ids reference resources that do not exist as the response itself + // specifies if the resource existed before the DeleteAuthToken request was + // received. + DeleteAuthToken(context.Context, *DeleteAuthTokenRequest) (*DeleteAuthTokenResponse, error) +} + +// UnimplementedAuthTokenServiceServer can be embedded to have forward compatible implementations. +type UnimplementedAuthTokenServiceServer struct { +} + +func (*UnimplementedAuthTokenServiceServer) GetAuthToken(context.Context, *GetAuthTokenRequest) (*GetAuthTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAuthToken not implemented") +} +func (*UnimplementedAuthTokenServiceServer) ListAuthTokens(context.Context, *ListAuthTokensRequest) (*ListAuthTokensResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAuthTokens not implemented") +} +func (*UnimplementedAuthTokenServiceServer) DeleteAuthToken(context.Context, *DeleteAuthTokenRequest) (*DeleteAuthTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteAuthToken not implemented") +} + +func RegisterAuthTokenServiceServer(s *grpc.Server, srv AuthTokenServiceServer) { + s.RegisterService(&_AuthTokenService_serviceDesc, srv) +} + +func _AuthTokenService_GetAuthToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAuthTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthTokenServiceServer).GetAuthToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.AuthTokenService/GetAuthToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthTokenServiceServer).GetAuthToken(ctx, req.(*GetAuthTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthTokenService_ListAuthTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAuthTokensRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthTokenServiceServer).ListAuthTokens(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.AuthTokenService/ListAuthTokens", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthTokenServiceServer).ListAuthTokens(ctx, req.(*ListAuthTokensRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthTokenService_DeleteAuthToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAuthTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthTokenServiceServer).DeleteAuthToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.AuthTokenService/DeleteAuthToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthTokenServiceServer).DeleteAuthToken(ctx, req.(*DeleteAuthTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _AuthTokenService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "controller.api.services.v1.AuthTokenService", + HandlerType: (*AuthTokenServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAuthToken", + Handler: _AuthTokenService_GetAuthToken_Handler, + }, + { + MethodName: "ListAuthTokens", + Handler: _AuthTokenService_ListAuthTokens_Handler, + }, + { + MethodName: "DeleteAuthToken", + Handler: _AuthTokenService_DeleteAuthToken_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "controller/api/services/v1/authtokens_service.proto", +} diff --git a/internal/gen/controller/api/services/authtokens_service.pb.gw.go b/internal/gen/controller/api/services/authtokens_service.pb.gw.go new file mode 100644 index 000000000..1a359d4f3 --- /dev/null +++ b/internal/gen/controller/api/services/authtokens_service.pb.gw.go @@ -0,0 +1,388 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: controller/api/services/v1/authtokens_service.proto + +/* +Package services is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package services + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +var ( + filter_AuthTokenService_GetAuthToken_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_AuthTokenService_GetAuthToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthTokenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAuthTokenRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthTokenService_GetAuthToken_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetAuthToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthTokenService_GetAuthToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthTokenServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAuthTokenRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthTokenService_GetAuthToken_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetAuthToken(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_AuthTokenService_ListAuthTokens_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_AuthTokenService_ListAuthTokens_0(ctx context.Context, marshaler runtime.Marshaler, client AuthTokenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAuthTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthTokenService_ListAuthTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListAuthTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthTokenService_ListAuthTokens_0(ctx context.Context, marshaler runtime.Marshaler, server AuthTokenServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAuthTokensRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthTokenService_ListAuthTokens_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListAuthTokens(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthTokenService_DeleteAuthToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthTokenServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAuthTokenRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.DeleteAuthToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthTokenService_DeleteAuthToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthTokenServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAuthTokenRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.DeleteAuthToken(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterAuthTokenServiceHandlerServer registers the http handlers for service AuthTokenService to "mux". +// UnaryRPC :call AuthTokenServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +func RegisterAuthTokenServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthTokenServiceServer) error { + + mux.Handle("GET", pattern_AuthTokenService_GetAuthToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthTokenService_GetAuthToken_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_GetAuthToken_0(ctx, mux, outboundMarshaler, w, req, response_AuthTokenService_GetAuthToken_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthTokenService_ListAuthTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthTokenService_ListAuthTokens_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_ListAuthTokens_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_AuthTokenService_DeleteAuthToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthTokenService_DeleteAuthToken_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_DeleteAuthToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterAuthTokenServiceHandlerFromEndpoint is same as RegisterAuthTokenServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAuthTokenServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAuthTokenServiceHandler(ctx, mux, conn) +} + +// RegisterAuthTokenServiceHandler registers the http handlers for service AuthTokenService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAuthTokenServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAuthTokenServiceHandlerClient(ctx, mux, NewAuthTokenServiceClient(conn)) +} + +// RegisterAuthTokenServiceHandlerClient registers the http handlers for service AuthTokenService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AuthTokenServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AuthTokenServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AuthTokenServiceClient" to call the correct interceptors. +func RegisterAuthTokenServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthTokenServiceClient) error { + + mux.Handle("GET", pattern_AuthTokenService_GetAuthToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthTokenService_GetAuthToken_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_GetAuthToken_0(ctx, mux, outboundMarshaler, w, req, response_AuthTokenService_GetAuthToken_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthTokenService_ListAuthTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthTokenService_ListAuthTokens_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_ListAuthTokens_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_AuthTokenService_DeleteAuthToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthTokenService_DeleteAuthToken_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthTokenService_DeleteAuthToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_AuthTokenService_GetAuthToken_0 struct { + proto.Message +} + +func (m response_AuthTokenService_GetAuthToken_0) XXX_ResponseBody() interface{} { + response := m.Message.(*GetAuthTokenResponse) + return response.Item +} + +var ( + pattern_AuthTokenService_GetAuthToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "scopes", "auth-tokens", "id"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_AuthTokenService_ListAuthTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 2, 2}, []string{"v1", "scopes", "auth-tokens"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_AuthTokenService_DeleteAuthToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "scopes", "auth-tokens", "id"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_AuthTokenService_GetAuthToken_0 = runtime.ForwardResponseMessage + + forward_AuthTokenService_ListAuthTokens_0 = runtime.ForwardResponseMessage + + forward_AuthTokenService_DeleteAuthToken_0 = runtime.ForwardResponseMessage +) diff --git a/internal/proto/local/controller/api/services/v1/authtokens_service.proto b/internal/proto/local/controller/api/services/v1/authtokens_service.proto new file mode 100644 index 000000000..d2f1b3b0d --- /dev/null +++ b/internal/proto/local/controller/api/services/v1/authtokens_service.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +package controller.api.services.v1; + +option go_package = "github.com/hashicorp/watchtower/internal/gen/controller/api/services;services"; + +import "protoc-gen-swagger/options/annotations.proto"; +import "google/api/annotations.proto"; +import "controller/api/resources/authtokens/v1/authtoken.proto"; + +service AuthTokenService { + + // GetAuthToken returns a stored auth token if present. The provided request + // must include the org and auth token id for the auth token being retrieved. If + // any of those ids are missing, malformed or reference a non existing + // resource an error is returned. + rpc GetAuthToken(GetAuthTokenRequest) returns (GetAuthTokenResponse) { + option (google.api.http) = { + get: "/v1/scopes/*/auth-tokens/{id}" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = { + summary: "Gets a single AuthToken" + }; + } + + // ListAuthTokens returns a list of stored auth tokens which exist inside the org + // referenced inside the request. The request must include the org id for + // the auth token being retrieved. If the org id is missing, malformed, or + // reference a non existing organization, an error is returned. + rpc ListAuthTokens(ListAuthTokensRequest) returns (ListAuthTokensResponse) { + option (google.api.http) = { + get: "/v1/scopes/*/auth-tokens" + }; + option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = { + summary: "Lists all AuthTokens" + }; + } + + // DeleteAuthToken removes a auth token from Watchtower. If the provided org or + // auth token ids are malformed or not provided an error is returned. No error is returned + // if either ids reference resources that do not exist as the response itself + // specifies if the resource existed before the DeleteAuthToken request was + // received. + rpc DeleteAuthToken(DeleteAuthTokenRequest) returns (DeleteAuthTokenResponse) { + option (google.api.http) = { + delete: "/v1/scopes/*/auth-tokens/{id}" + }; + option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = { + summary: "Deletes an AuthTokens" + }; + } +} + +message GetAuthTokenRequest { + string id = 1; + string view = 2; +} + +message GetAuthTokenResponse { + resources.authtokens.v1.AuthToken item = 1; +} + +message ListAuthTokensRequest { + string view = 1; +} + +message ListAuthTokensResponse { + repeated resources.authtokens.v1.AuthToken items = 1; +} + +message DeleteAuthTokenRequest { + string id = 1; +} + +message DeleteAuthTokenResponse { + bool existed = 1; +} \ No newline at end of file diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 5049a8238..e43d2c873 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/watchtower/internal/servers/controller/handlers" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/authenticate" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers/authtokens" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/groups" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_catalogs" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/roles" @@ -69,6 +70,13 @@ func handleGrpcGateway(c *Controller) (http.Handler, error) { if err := services.RegisterAuthenticationServiceHandlerServer(ctx, mux, auths); err != nil { return nil, fmt.Errorf("failed to register authenticate service handler: %w", err) } + authtoks, err := authtokens.NewService(c.AuthTokenRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create auth token handler service: %w", err) + } + if err := services.RegisterAuthTokenServiceHandlerServer(ctx, mux, authtoks); err != nil { + return nil, fmt.Errorf("failed to register auth token service handler: %w", err) + } os, err := scopes.NewService(c.IamRepoFn) if err != nil { return nil, fmt.Errorf("failed to create scope handler service: %w", err) diff --git a/internal/servers/controller/handler_test.go b/internal/servers/controller/handler_test.go index 34f4ca34d..0e0266753 100644 --- a/internal/servers/controller/handler_test.go +++ b/internal/servers/controller/handler_test.go @@ -42,24 +42,28 @@ func TestHandleImplementedPaths(t *testing.T) { for verb, paths := range map[string][]string{ "GET": { + "v1/scopes", "v1/scopes/someid", - "v1/scopes/someid/users", - "v1/scopes/someid/users/someid", - "v1/scopes/someid/roles", - "v1/scopes/someid/roles/someid", + "v1/scopes/someid/auth-tokens", + "v1/scopes/someid/auth-tokens/someid", "v1/scopes/someid/groups", "v1/scopes/someid/groups/someid", "v1/scopes/someid/host-catalogs", "v1/scopes/someid/host-catalogs/someid", + "v1/scopes/someid/roles", + "v1/scopes/someid/roles/someid", + "v1/scopes/someid/users", + "v1/scopes/someid/users/someid", }, "POST": { // Creation end points "v1/scopes", - "v1/scopes/someid/users", - "v1/scopes/someid/roles", "v1/scopes/someid/groups", + "v1/scopes/someid/roles", + "v1/scopes/someid/users", // custom methods + "v1/scopes/someid/auth-methods/someid:authenticate", "v1/scopes/someid/roles/someid:add-principals", "v1/scopes/someid/roles/someid:set-principals", "v1/scopes/someid/roles/someid:remove-principals", @@ -75,6 +79,7 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/scopes/someid/users/someid", "v1/scopes/someid/roles/someid", "v1/scopes/someid/groups/someid", + "v1/scopes/someid/auth-tokens/someid", }, "PATCH": { "v1/scopes/someid", diff --git a/internal/servers/controller/handlers/authtokens/authtoken_service.go b/internal/servers/controller/handlers/authtokens/authtoken_service.go new file mode 100644 index 000000000..7fa4d0f9f --- /dev/null +++ b/internal/servers/controller/handlers/authtokens/authtoken_service.go @@ -0,0 +1,197 @@ +package authtokens + +import ( + "context" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/watchtower/internal/auth" + "github.com/hashicorp/watchtower/internal/authtoken" + "github.com/hashicorp/watchtower/internal/db" + pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authtokens" + pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const orgIdFieldName = "org_id" + +var ( + reInvalidID = regexp.MustCompile("[^A-Za-z0-9]") +) + +// Service handles request as described by the pbs.AuthTokenServiceServer interface. +type Service struct { + repoFn func() (*authtoken.Repository, error) +} + +// NewService returns a user service which handles user related requests to watchtower. +func NewService(repo func() (*authtoken.Repository, error)) (Service, error) { + if repo == nil { + return Service{}, fmt.Errorf("nil iam repository provided") + } + return Service{repoFn: repo}, nil +} + +var _ pbs.AuthTokenServiceServer = Service{} + +// ListAuthTokens implements the interface pbs.AuthTokenServiceServer. +func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequest) (*pbs.ListAuthTokensResponse, error) { + authResults := auth.Verify(ctx) + if !authResults.Valid { + return nil, handlers.ForbiddenError() + } + if err := validateListRequest(req); err != nil { + return nil, err + } + ul, err := s.listFromRepo(ctx, authResults.Scope.GetId()) + if err != nil { + return nil, err + } + for _, item := range ul { + item.Scope = authResults.Scope + } + return &pbs.ListAuthTokensResponse{Items: ul}, nil +} + +// GetAuthTokens implements the interface pbs.AuthTokenServiceServer. +func (s Service) GetAuthToken(ctx context.Context, req *pbs.GetAuthTokenRequest) (*pbs.GetAuthTokenResponse, error) { + authResults := auth.Verify(ctx) + if !authResults.Valid { + return nil, handlers.ForbiddenError() + } + if err := validateGetRequest(req); err != nil { + return nil, err + } + u, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + u.Scope = authResults.Scope + return &pbs.GetAuthTokenResponse{Item: u}, nil +} + +// DeleteAuthToken implements the interface pbs.AuthTokenServiceServer. +func (s Service) DeleteAuthToken(ctx context.Context, req *pbs.DeleteAuthTokenRequest) (*pbs.DeleteAuthTokenResponse, error) { + authResults := auth.Verify(ctx) + if !authResults.Valid { + return nil, handlers.ForbiddenError() + } + if err := validateDeleteRequest(req); err != nil { + return nil, err + } + existed, err := s.deleteFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + return &pbs.DeleteAuthTokenResponse{Existed: existed}, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (*pb.AuthToken, error) { + repo, err := s.repoFn() + if err != nil { + return nil, err + } + u, err := repo.LookupAuthToken(ctx, id) + if err != nil { + if errors.Is(err, db.ErrRecordNotFound) { + return nil, handlers.NotFoundErrorf("AuthToken %q doesn't exist.", id) + } + return nil, err + } + if u == nil { + return nil, handlers.NotFoundErrorf("AuthToken %q doesn't exist.", id) + } + return toProto(u), nil +} + +func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { + repo, err := s.repoFn() + if err != nil { + return false, err + } + rows, err := repo.DeleteAuthToken(ctx, id) + if err != nil { + if errors.Is(err, db.ErrRecordNotFound) { + return false, nil + } + return false, status.Errorf(codes.Internal, "Unable to delete user: %v.", err) + } + return rows > 0, nil +} + +func (s Service) listFromRepo(ctx context.Context, orgId string) ([]*pb.AuthToken, error) { + repo, err := s.repoFn() + _ = repo + if err != nil { + return nil, err + } + ul, err := repo.ListAuthTokens(ctx, orgId) + if err != nil { + return nil, err + } + var outUl []*pb.AuthToken + for _, u := range ul { + outUl = append(outUl, toProto(u)) + } + return outUl, nil +} + +func toProto(in *authtoken.AuthToken) *pb.AuthToken { + out := pb.AuthToken{ + Id: in.GetPublicId(), + CreatedTime: in.GetCreateTime().GetTimestamp(), + UpdatedTime: in.GetUpdateTime().GetTimestamp(), + ApproximateLastUsedTime: in.GetApproximateLastAccessTime().GetTimestamp(), + ExpirationTime: in.GetExpirationTime().GetTimestamp(), + UserId: in.GetIamUserId(), + AuthMethodId: in.GetAuthMethodId(), + } + return &out +} + +// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce +// requirements on the structure of the request. They verify that: +// * The path passed in is correctly formatted +// * All required parameters are set +// * There are no conflicting parameters provided +func validateGetRequest(req *pbs.GetAuthTokenRequest) error { + badFields := map[string]string{} + if !validId(req.GetId(), authtoken.AuthTokenPrefix+"_") { + badFields["id"] = "Invalid formatted user id." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} + +func validateDeleteRequest(req *pbs.DeleteAuthTokenRequest) error { + badFields := map[string]string{} + if !validId(req.GetId(), authtoken.AuthTokenPrefix+"_") { + badFields["id"] = "Incorrectly formatted identifier." + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + return nil +} + +func validateListRequest(req *pbs.ListAuthTokensRequest) error { + badFields := map[string]string{} + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} + +func validId(id, prefix string) bool { + if !strings.HasPrefix(id, prefix) { + return false + } + id = strings.TrimPrefix(id, prefix) + return !reInvalidID.Match([]byte(id)) +} diff --git a/internal/servers/controller/handlers/authtokens/authtoken_service_test.go b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go new file mode 100644 index 000000000..3ef6ca6b4 --- /dev/null +++ b/internal/servers/controller/handlers/authtokens/authtoken_service_test.go @@ -0,0 +1,278 @@ +package authtokens_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/watchtower/internal/auth" + "github.com/hashicorp/watchtower/internal/authtoken" + "github.com/hashicorp/watchtower/internal/db" + pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/authtokens" + "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" + "github.com/hashicorp/watchtower/internal/iam" + "github.com/hashicorp/watchtower/internal/servers/controller/handlers/authtokens" + "github.com/hashicorp/watchtower/internal/types/scope" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGet(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, wrap) + } + + s, err := authtokens.NewService(repoFn) + require.NoError(t, err, "Couldn't create new auth token service.") + + org, _ := iam.TestScopes(t, conn) + at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + + wireAuthToken := pb.AuthToken{ + Id: at.GetPublicId(), + UserId: at.GetIamUserId(), + AuthMethodId: at.GetAuthMethodId(), + CreatedTime: at.GetCreateTime().GetTimestamp(), + UpdatedTime: at.GetUpdateTime().GetTimestamp(), + ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(), + ExpirationTime: at.GetExpirationTime().GetTimestamp(), + Scope: &scopes.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String()}, + } + + cases := []struct { + name string + req *pbs.GetAuthTokenRequest + res *pbs.GetAuthTokenResponse + errCode codes.Code + }{ + { + name: "Get an existing auth token", + req: &pbs.GetAuthTokenRequest{Id: wireAuthToken.GetId()}, + res: &pbs.GetAuthTokenResponse{Item: &wireAuthToken}, + errCode: codes.OK, + }, + { + name: "Get a non existing auth token", + req: &pbs.GetAuthTokenRequest{Id: authtoken.AuthTokenPrefix + "_DoesntExis"}, + res: nil, + errCode: codes.NotFound, + }, + { + name: "Wrong id prefix", + req: &pbs.GetAuthTokenRequest{Id: "j_1234567890"}, + res: nil, + errCode: codes.InvalidArgument, + }, + { + name: "space in id", + req: &pbs.GetAuthTokenRequest{Id: authtoken.AuthTokenPrefix + "_1 23456789"}, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + got, gErr := s.GetAuthToken(auth.DisabledAuthTestContext(auth.WithScopeId(org.GetPublicId())), tc.req) + assert.Equal(tc.errCode, status.Code(gErr), "GetOrg(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode) + assert.True(proto.Equal(got, tc.res), "GetOrg(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestList(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, wrap) + } + + orgNoTokens, _ := iam.TestScopes(t, conn) + + orgWithSomeTokens, _ := iam.TestScopes(t, conn) + var wantSomeTokens []*pb.AuthToken + for i := 0; i < 3; i++ { + at := authtoken.TestAuthToken(t, conn, wrap, orgWithSomeTokens.GetPublicId()) + wantSomeTokens = append(wantSomeTokens, &pb.AuthToken{ + Id: at.GetPublicId(), + UserId: at.GetIamUserId(), + AuthMethodId: at.GetAuthMethodId(), + CreatedTime: at.GetCreateTime().GetTimestamp(), + UpdatedTime: at.GetUpdateTime().GetTimestamp(), + ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(), + ExpirationTime: at.GetExpirationTime().GetTimestamp(), + Scope: &scopes.ScopeInfo{Id: orgWithSomeTokens.GetPublicId(), Type: scope.Org.String()}, + }) + } + + orgWithOtherTokens, _ := iam.TestScopes(t, conn) + var wantOtherTokens []*pb.AuthToken + for i := 0; i < 3; i++ { + at := authtoken.TestAuthToken(t, conn, wrap, orgWithOtherTokens.GetPublicId()) + wantOtherTokens = append(wantOtherTokens, &pb.AuthToken{ + Id: at.GetPublicId(), + UserId: at.GetIamUserId(), + AuthMethodId: at.GetAuthMethodId(), + CreatedTime: at.GetCreateTime().GetTimestamp(), + UpdatedTime: at.GetUpdateTime().GetTimestamp(), + ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(), + ExpirationTime: at.GetExpirationTime().GetTimestamp(), + Scope: &scopes.ScopeInfo{Id: orgWithOtherTokens.GetPublicId(), Type: scope.Org.String()}, + }) + } + + cases := []struct { + name string + scope string + res *pbs.ListAuthTokensResponse + errCode codes.Code + }{ + { + name: "List Some Tokens", + scope: orgWithSomeTokens.GetPublicId(), + res: &pbs.ListAuthTokensResponse{Items: wantSomeTokens}, + errCode: codes.OK, + }, + { + name: "List Other Tokens", + scope: orgWithOtherTokens.GetPublicId(), + res: &pbs.ListAuthTokensResponse{Items: wantOtherTokens}, + errCode: codes.OK, + }, + { + name: "List No Token", + scope: orgNoTokens.GetPublicId(), + res: &pbs.ListAuthTokensResponse{}, + errCode: codes.OK, + }, + // TODO: When an org doesn't exist, we should return a 404 instead of an empty list. + { + name: "Unfound Org", + scope: scope.Org.Prefix() + "_DoesntExis", + res: &pbs.ListAuthTokensResponse{}, + errCode: codes.OK, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s, err := authtokens.NewService(repoFn) + require.NoError(t, err, "Couldn't create new user service.") + + got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(auth.WithScopeId(tc.scope)), &pbs.ListAuthTokensRequest{}) + assert.Equal(t, tc.errCode, status.Code(gErr), "ListUsers() with scope %q got error %v, wanted %v", tc.scope, gErr, tc.errCode) + assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "ListUsers() with scope %q got response %q, wanted %q", tc.scope, got, tc.res) + }) + } +} + +func TestDelete(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, wrap) + } + + wrongOrg, _ := iam.TestScopes(t, conn) + org, _ := iam.TestScopes(t, conn) + at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + atForWrongOrg := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + + s, err := authtokens.NewService(repoFn) + require.NoError(t, err, "Error when getting new user service.") + + cases := []struct { + name string + scope string + req *pbs.DeleteAuthTokenRequest + res *pbs.DeleteAuthTokenResponse + errCode codes.Code + }{ + { + name: "Delete an existing token", + scope: org.GetPublicId(), + req: &pbs.DeleteAuthTokenRequest{ + Id: at.GetPublicId(), + }, + res: &pbs.DeleteAuthTokenResponse{ + Existed: true, + }, + errCode: codes.OK, + }, + { + name: "Delete token from wrong scope", + scope: wrongOrg.GetPublicId(), + req: &pbs.DeleteAuthTokenRequest{ + Id: atForWrongOrg.GetPublicId(), + }, + // TODO(toddknight): This should return Existed:false. Figure out if this test is testing something valid + // and if so make it pass. + res: &pbs.DeleteAuthTokenResponse{ + Existed: true, + }, + errCode: codes.OK, + }, + { + name: "Delete bad token id", + scope: org.GetPublicId(), + req: &pbs.DeleteAuthTokenRequest{ + Id: authtoken.AuthTokenPrefix + "_doesntexis", + }, + res: &pbs.DeleteAuthTokenResponse{ + Existed: false, + }, + errCode: codes.OK, + }, + { + name: "Bad token id formatting", + scope: org.GetPublicId(), + req: &pbs.DeleteAuthTokenRequest{ + Id: "bad_format", + }, + res: nil, + errCode: codes.InvalidArgument, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + got, gErr := s.DeleteAuthToken(auth.DisabledAuthTestContext(auth.WithScopeId(tc.scope)), tc.req) + assert.Equal(tc.errCode, status.Code(gErr), "DeleteUser(%+v) got error %v, wanted %v", tc.req, gErr, tc.errCode) + assert.EqualValuesf(tc.res, got, "DeleteUser(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestDelete_twice(t *testing.T) { + assert := assert.New(t) + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + repoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, wrap) + } + + org, _ := iam.TestScopes(t, conn) + at := authtoken.TestAuthToken(t, conn, wrap, org.GetPublicId()) + + s, err := authtokens.NewService(repoFn) + require.NoError(t, err, "Error when getting new user service") + req := &pbs.DeleteAuthTokenRequest{ + Id: at.GetPublicId(), + } + got, gErr := s.DeleteAuthToken(auth.DisabledAuthTestContext(auth.WithScopeId(at.GetScopeId())), req) + assert.NoError(gErr, "First attempt") + assert.True(got.GetExisted(), "Expected existed to be true for the first delete.") + got, gErr = s.DeleteAuthToken(auth.DisabledAuthTestContext(auth.WithScopeId(at.GetScopeId())), req) + assert.NoError(gErr, "Second attempt") + assert.False(got.GetExisted(), "Expected existed to be false for the second delete.") +} diff --git a/internal/types/resource/resource.go b/internal/types/resource/resource.go index f59c05158..1a78a6857 100644 --- a/internal/types/resource/resource.go +++ b/internal/types/resource/resource.go @@ -26,7 +26,8 @@ const ( Global Type = 19 // TODO: remove this after demo - Group Type = 20 + Group Type = 20 + AuthToken Type = 21 ) func (r Type) String() string { @@ -52,6 +53,7 @@ func (r Type) String() string { "target", "global", "group", + "auth-token", }[r] } @@ -76,4 +78,5 @@ var Map = map[string]Type{ Target.String(): Target, Global.String(): Global, Group.String(): Group, + AuthToken.String(): AuthToken, }