|
15 | 15 | package identityserver |
16 | 16 |
|
17 | 17 | import ( |
| 18 | + "context" |
18 | 19 | "testing" |
19 | 20 | "time" |
20 | 21 |
|
| 22 | + "go.thethings.network/lorawan-stack/v3/pkg/auth" |
| 23 | + "go.thethings.network/lorawan-stack/v3/pkg/auth/pbkdf2" |
21 | 24 | "go.thethings.network/lorawan-stack/v3/pkg/errors" |
22 | 25 | "go.thethings.network/lorawan-stack/v3/pkg/identityserver/storetest" |
| 26 | + "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" |
23 | 27 | "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" |
24 | 28 | "go.thethings.network/lorawan-stack/v3/pkg/util/test" |
25 | 29 | "go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should" |
@@ -64,7 +68,86 @@ func TestEntityAccess(t *testing.T) { |
64 | 68 | storedKey.ExpiresAt = timestamppb.New(time.Now().Add(-10 * time.Minute)) |
65 | 69 | expiredCreds := rpcCreds(expiredKey) |
66 | 70 |
|
| 71 | + oauthUsr := p.NewUser() |
| 72 | + oauthClient := p.NewClient(oauthUsr.GetOrganizationOrUserIdentifiers()) |
| 73 | + oauthClient.Rights = []ttnpb.Right{ttnpb.Right_RIGHT_USER_ALL} |
| 74 | + |
| 75 | + // Session that is already expired. |
| 76 | + expiredSession := p.NewUserSession(oauthUsr.GetIds()) |
| 77 | + p.UserSessions[len(p.UserSessions)-1].ExpiresAt = timestamppb.New(time.Now().Add(-10 * time.Minute)) |
| 78 | + |
| 79 | + // Session that is still valid. |
| 80 | + validSession := p.NewUserSession(oauthUsr.GetIds()) |
| 81 | + p.UserSessions[len(p.UserSessions)-1].ExpiresAt = timestamppb.New(time.Now().Add(10 * time.Minute)) |
| 82 | + |
| 83 | + // Generate access token bearer strings. The stored AccessToken is the hashed key. |
| 84 | + newBearerAccessToken := func() (bearer, tokenID, hashed string) { |
| 85 | + t.Helper() |
| 86 | + raw, err := auth.AccessToken.Generate(context.Background(), "") |
| 87 | + if err != nil { |
| 88 | + t.Fatal(err) |
| 89 | + } |
| 90 | + _, tokenID, key, err := auth.SplitToken(raw) |
| 91 | + if err != nil { |
| 92 | + t.Fatal(err) |
| 93 | + } |
| 94 | + hashValidator := pbkdf2.Default() |
| 95 | + hashValidator.Iterations = 10 |
| 96 | + hashed, err = auth.Hash(auth.NewContextWithHashValidator(context.Background(), hashValidator), key) |
| 97 | + if err != nil { |
| 98 | + t.Fatal(err) |
| 99 | + } |
| 100 | + return raw, tokenID, hashed |
| 101 | + } |
| 102 | + |
| 103 | + expiredSessionToken, expiredSessionTokenID, expiredSessionTokenHash := newBearerAccessToken() |
| 104 | + missingSessionToken, missingSessionTokenID, missingSessionTokenHash := newBearerAccessToken() |
| 105 | + validSessionToken, validSessionTokenID, validSessionTokenHash := newBearerAccessToken() |
| 106 | + |
| 107 | + bearerCreds := func(bearer string) grpc.CallOption { |
| 108 | + return grpc.PerRPCCredentials(rpcmetadata.MD{ |
| 109 | + AuthType: "bearer", |
| 110 | + AuthValue: bearer, |
| 111 | + AllowInsecure: true, |
| 112 | + }) |
| 113 | + } |
| 114 | + |
67 | 115 | testWithIdentityServer(t, func(is *IdentityServer, cc *grpc.ClientConn) { |
| 116 | + ctx := test.Context() |
| 117 | + if _, err := is.store.CreateAccessToken(ctx, &ttnpb.OAuthAccessToken{ |
| 118 | + UserIds: oauthUsr.GetIds(), |
| 119 | + ClientIds: oauthClient.GetIds(), |
| 120 | + UserSessionId: expiredSession.GetSessionId(), |
| 121 | + Id: expiredSessionTokenID, |
| 122 | + AccessToken: expiredSessionTokenHash, |
| 123 | + Rights: oauthClient.GetRights(), |
| 124 | + ExpiresAt: timestamppb.New(time.Now().Add(time.Hour)), |
| 125 | + }, ""); err != nil { |
| 126 | + t.Fatal(err) |
| 127 | + } |
| 128 | + if _, err := is.store.CreateAccessToken(ctx, &ttnpb.OAuthAccessToken{ |
| 129 | + UserIds: oauthUsr.GetIds(), |
| 130 | + ClientIds: oauthClient.GetIds(), |
| 131 | + UserSessionId: "00000000-0000-0000-0000-000000000000", |
| 132 | + Id: missingSessionTokenID, |
| 133 | + AccessToken: missingSessionTokenHash, |
| 134 | + Rights: oauthClient.GetRights(), |
| 135 | + ExpiresAt: timestamppb.New(time.Now().Add(time.Hour)), |
| 136 | + }, ""); err != nil { |
| 137 | + t.Fatal(err) |
| 138 | + } |
| 139 | + if _, err := is.store.CreateAccessToken(ctx, &ttnpb.OAuthAccessToken{ |
| 140 | + UserIds: oauthUsr.GetIds(), |
| 141 | + ClientIds: oauthClient.GetIds(), |
| 142 | + UserSessionId: validSession.GetSessionId(), |
| 143 | + Id: validSessionTokenID, |
| 144 | + AccessToken: validSessionTokenHash, |
| 145 | + Rights: oauthClient.GetRights(), |
| 146 | + ExpiresAt: timestamppb.New(time.Now().Add(time.Hour)), |
| 147 | + }, ""); err != nil { |
| 148 | + t.Fatal(err) |
| 149 | + } |
| 150 | + |
68 | 151 | is.config.UserRegistration.ContactInfoValidation.Required = true |
69 | 152 |
|
70 | 153 | cli := ttnpb.NewEntityAccessClient(cc) |
@@ -166,5 +249,29 @@ func TestEntityAccess(t *testing.T) { |
166 | 249 | a.So(errors.IsUnauthenticated(err), should.BeTrue) |
167 | 250 | } |
168 | 251 | }) |
| 252 | + |
| 253 | + t.Run("Access Token with Valid Session", func(t *testing.T) { |
| 254 | + a, ctx := test.New(t) |
| 255 | + authInfo, err := cli.AuthInfo(ctx, ttnpb.Empty, bearerCreds(validSessionToken)) |
| 256 | + if a.So(err, should.BeNil) && a.So(authInfo, should.NotBeNil) { |
| 257 | + a.So(authInfo.GetOauthAccessToken(), should.NotBeNil) |
| 258 | + } |
| 259 | + }) |
| 260 | + |
| 261 | + t.Run("Access Token with Expired Session", func(t *testing.T) { |
| 262 | + a, ctx := test.New(t) |
| 263 | + _, err := cli.AuthInfo(ctx, ttnpb.Empty, bearerCreds(expiredSessionToken)) |
| 264 | + if a.So(err, should.NotBeNil) { |
| 265 | + a.So(errors.IsUnauthenticated(err), should.BeTrue) |
| 266 | + } |
| 267 | + }) |
| 268 | + |
| 269 | + t.Run("Access Token with Missing Session", func(t *testing.T) { |
| 270 | + a, ctx := test.New(t) |
| 271 | + _, err := cli.AuthInfo(ctx, ttnpb.Empty, bearerCreds(missingSessionToken)) |
| 272 | + if a.So(err, should.NotBeNil) { |
| 273 | + a.So(errors.IsUnauthenticated(err), should.BeTrue) |
| 274 | + } |
| 275 | + }) |
169 | 276 | }, withPrivateTestDatabase(p)) |
170 | 277 | } |
0 commit comments