diff --git a/pkg/identityserver/bunstore/oauth_store.go b/pkg/identityserver/bunstore/oauth_store.go index 025e992ad4..527b1043b4 100644 --- a/pkg/identityserver/bunstore/oauth_store.go +++ b/pkg/identityserver/bunstore/oauth_store.go @@ -685,6 +685,11 @@ func (s *oauthStore) GetAccessToken(ctx context.Context, id string) (*ttnpb.OAut return nil, err } + // NOTE: This imposes a limitation on the client's rights if the token's user is the unique support user. + if model.User.Account.UID == ttnpb.SupportUserID { + model.Rights = convertIntSlice[ttnpb.Right, int](ttnpb.AllReadAdminRights.GetRights()) + } + pb, err := accessTokenToPB(model, nil, nil) if err != nil { return nil, err diff --git a/pkg/identityserver/bunstore/store_test.go b/pkg/identityserver/bunstore/store_test.go index 746bcbe0d7..f1a8fc7148 100644 --- a/pkg/identityserver/bunstore/store_test.go +++ b/pkg/identityserver/bunstore/store_test.go @@ -185,6 +185,7 @@ func TestOAuthStore(t *testing.T) { st := storetest.New(t, newTestStore) st.TestOAuthStore(t) + st.TestOAuthStoreSupportUser(t) st.TestOAuthStorePagination(t) st.TestOAuthStorePaginationDefaults(t) } diff --git a/pkg/identityserver/storetest/oauth_store.go b/pkg/identityserver/storetest/oauth_store.go index b6cf59f01e..df9bdeacd6 100644 --- a/pkg/identityserver/storetest/oauth_store.go +++ b/pkg/identityserver/storetest/oauth_store.go @@ -356,6 +356,67 @@ func (st *StoreTest) TestOAuthStore(t *T) { }) } +// TestOAuthStoreSupportUser tests the behavior of AccessTokens with the unique support userID. +func (st *StoreTest) TestOAuthStoreSupportUser(t *T) { + supportUsr := st.population.NewUser() + supportUsr.Ids.UserId = ttnpb.SupportUserID + supportUsr.Name = "support" + supportUsr.PrimaryEmailAddress = "support@dummy-email.com" + + ses1 := st.population.NewUserSession(supportUsr.GetIds()) + cli1 := st.population.NewClient(nil) + cli1.SkipAuthorization = true + + s, ok := st.PrepareDB(t).(interface { + Store + is.OAuthStore + }) + defer st.DestroyDB(t, true, "users", "accounts", "user_sessions", "clients") + if !ok { + t.Skip("Store does not implement OAuthStore") + } + defer s.Close() + + var createdAccessToken *ttnpb.OAuthAccessToken + + t.Run("CreateAccessToken", func(t *T) { + a, ctx := test.New(t) + var err error + start := time.Now().Truncate(time.Second) + + createdAccessToken, err = s.CreateAccessToken(ctx, &ttnpb.OAuthAccessToken{ + UserIds: supportUsr.GetIds(), + UserSessionId: ses1.GetSessionId(), + ClientIds: cli1.GetIds(), + Id: "token_id", + AccessToken: "access_token", + RefreshToken: "refresh_token", + Rights: []ttnpb.Right{ttnpb.Right_RIGHT_ALL}, + ExpiresAt: timestamppb.New(start.Add(5 * time.Minute)), + }, "") + if a.So(err, should.BeNil) && a.So(createdAccessToken, should.NotBeNil) { + a.So(createdAccessToken.UserIds, should.Resemble, supportUsr.GetIds()) + a.So(createdAccessToken.UserSessionId, should.Equal, ses1.GetSessionId()) + a.So(createdAccessToken.ClientIds, should.Resemble, cli1.GetIds()) + a.So(createdAccessToken.Id, should.Equal, "token_id") + a.So(createdAccessToken.AccessToken, should.Equal, "access_token") + a.So(createdAccessToken.RefreshToken, should.Equal, "refresh_token") + a.So(createdAccessToken.Rights, should.Resemble, []ttnpb.Right{ttnpb.Right_RIGHT_ALL}) + a.So(*ttnpb.StdTime(createdAccessToken.ExpiresAt), should.Equal, start.Add(5*time.Minute)) + a.So(*ttnpb.StdTime(createdAccessToken.CreatedAt), should.HappenWithin, 5*time.Second, start) + } + }) + + t.Run("GetAccessToken", func(t *T) { + a, ctx := test.New(t) + got, err := s.GetAccessToken(ctx, "token_id") + if a.So(err, should.BeNil) && a.So(got, should.NotBeNil) { + // NOTE: This should be limited due to referencing the unique support userID. + a.So(got.Rights, should.Resemble, ttnpb.AllReadAdminRights.GetRights()) + } + }) +} + func (st *StoreTest) TestOAuthStorePagination(t *T) { a, ctx := test.New(t)