@@ -14,16 +14,20 @@ import (
14
14
"testing"
15
15
"time"
16
16
17
+ "github.com/google/uuid"
17
18
"github.com/stretchr/testify/assert"
18
19
"github.com/stretchr/testify/require"
20
+ "golang.org/x/exp/slices"
19
21
"golang.org/x/oauth2"
20
22
21
23
"github.com/coder/coder/v2/coderd/database"
24
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
22
25
"github.com/coder/coder/v2/coderd/database/dbgen"
23
26
"github.com/coder/coder/v2/coderd/database/dbmem"
24
27
"github.com/coder/coder/v2/coderd/database/dbtime"
25
28
"github.com/coder/coder/v2/coderd/httpapi"
26
29
"github.com/coder/coder/v2/coderd/httpmw"
30
+ "github.com/coder/coder/v2/coderd/rbac"
27
31
"github.com/coder/coder/v2/codersdk"
28
32
"github.com/coder/coder/v2/cryptorand"
29
33
"github.com/coder/coder/v2/testutil"
@@ -38,6 +42,37 @@ func randomAPIKeyParts() (id string, secret string) {
38
42
func TestAPIKey (t * testing.T ) {
39
43
t .Parallel ()
40
44
45
+ // assertActorOk asserts all the properties of the user auth are ok.
46
+ assertActorOk := func (t * testing.T , r * http.Request ) {
47
+ t .Helper ()
48
+
49
+ actor , ok := dbauthz .ActorFromContext (r .Context ())
50
+ assert .True (t , ok , "dbauthz actor ok" )
51
+ if ok {
52
+ _ , err := actor .Roles .Expand ()
53
+ assert .NoError (t , err , "actor roles ok" )
54
+
55
+ _ , err = actor .Scope .Expand ()
56
+ assert .NoError (t , err , "actor scope ok" )
57
+
58
+ err = actor .RegoValueOk ()
59
+ assert .NoError (t , err , "actor rego ok" )
60
+ }
61
+
62
+ auth , ok := httpmw .UserAuthorizationOptional (r )
63
+ assert .True (t , ok , "httpmw auth ok" )
64
+ if ok {
65
+ _ , err := auth .Roles .Expand ()
66
+ assert .NoError (t , err , "auth roles ok" )
67
+
68
+ _ , err = auth .Scope .Expand ()
69
+ assert .NoError (t , err , "auth scope ok" )
70
+
71
+ err = auth .RegoValueOk ()
72
+ assert .NoError (t , err , "auth rego ok" )
73
+ }
74
+ }
75
+
41
76
successHandler := http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
42
77
// Only called if the API key passes through the handler.
43
78
httpapi .Write (context .Background (), rw , http .StatusOK , codersdk.Response {
@@ -256,6 +291,7 @@ func TestAPIKey(t *testing.T) {
256
291
})(http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
257
292
// Checks that it exists on the context!
258
293
_ = httpmw .APIKey (r )
294
+ assertActorOk (t , r )
259
295
httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.Response {
260
296
Message : "It worked!" ,
261
297
})
@@ -296,6 +332,7 @@ func TestAPIKey(t *testing.T) {
296
332
// Checks that it exists on the context!
297
333
apiKey := httpmw .APIKey (r )
298
334
assert .Equal (t , database .APIKeyScopeApplicationConnect , apiKey .Scope )
335
+ assertActorOk (t , r )
299
336
300
337
httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.Response {
301
338
Message : "it worked!" ,
@@ -330,6 +367,8 @@ func TestAPIKey(t *testing.T) {
330
367
})(http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
331
368
// Checks that it exists on the context!
332
369
_ = httpmw .APIKey (r )
370
+ assertActorOk (t , r )
371
+
333
372
httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.Response {
334
373
Message : "It worked!" ,
335
374
})
@@ -633,7 +672,7 @@ func TestAPIKey(t *testing.T) {
633
672
require .Equal (t , sentAPIKey .LoginType , gotAPIKey .LoginType )
634
673
})
635
674
636
- t .Run ("MissongConfig " , func (t * testing.T ) {
675
+ t .Run ("MissingConfig " , func (t * testing.T ) {
637
676
t .Parallel ()
638
677
var (
639
678
db = dbmem .New ()
@@ -667,4 +706,133 @@ func TestAPIKey(t *testing.T) {
667
706
out , _ := io .ReadAll (res .Body )
668
707
require .Contains (t , string (out ), "Unable to refresh" )
669
708
})
709
+
710
+ t .Run ("CustomRoles" , func (t * testing.T ) {
711
+ t .Parallel ()
712
+ var (
713
+ db = dbmem .New ()
714
+ org = dbgen .Organization (t , db , database.Organization {})
715
+ customRole = dbgen .CustomRole (t , db , database.CustomRole {
716
+ Name : "custom-role" ,
717
+ OrgPermissions : []database.CustomRolePermission {},
718
+ OrganizationID : uuid.NullUUID {
719
+ UUID : org .ID ,
720
+ Valid : true ,
721
+ },
722
+ })
723
+ user = dbgen .User (t , db , database.User {
724
+ RBACRoles : []string {},
725
+ })
726
+ _ = dbgen .OrganizationMember (t , db , database.OrganizationMember {
727
+ UserID : user .ID ,
728
+ OrganizationID : org .ID ,
729
+ CreatedAt : time.Time {},
730
+ UpdatedAt : time.Time {},
731
+ Roles : []string {
732
+ rbac .RoleOrgAdmin (),
733
+ customRole .Name ,
734
+ },
735
+ })
736
+ _ , token = dbgen .APIKey (t , db , database.APIKey {
737
+ UserID : user .ID ,
738
+ ExpiresAt : dbtime .Now ().AddDate (0 , 0 , 1 ),
739
+ })
740
+
741
+ r = httptest .NewRequest ("GET" , "/" , nil )
742
+ rw = httptest .NewRecorder ()
743
+ )
744
+ r .Header .Set (codersdk .SessionTokenHeader , token )
745
+
746
+ httpmw .ExtractAPIKeyMW (httpmw.ExtractAPIKeyConfig {
747
+ DB : db ,
748
+ RedirectToLogin : false ,
749
+ })(http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
750
+ assertActorOk (t , r )
751
+
752
+ auth := httpmw .UserAuthorization (r )
753
+
754
+ roles , err := auth .Roles .Expand ()
755
+ assert .NoError (t , err , "expand user roles" )
756
+ // Assert built in org role
757
+ assert .True (t , slices .ContainsFunc (roles , func (role rbac.Role ) bool {
758
+ return role .Identifier .Name == rbac .RoleOrgAdmin () && role .Identifier .OrganizationID == org .ID
759
+ }), "org admin role" )
760
+ // Assert custom role
761
+ assert .True (t , slices .ContainsFunc (roles , func (role rbac.Role ) bool {
762
+ return role .Identifier .Name == customRole .Name && role .Identifier .OrganizationID == org .ID
763
+ }), "custom org role" )
764
+
765
+ httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.Response {
766
+ Message : "It worked!" ,
767
+ })
768
+ })).ServeHTTP (rw , r )
769
+ res := rw .Result ()
770
+ defer res .Body .Close ()
771
+ require .Equal (t , http .StatusOK , res .StatusCode )
772
+ })
773
+
774
+ // There is no sql foreign key constraint to require all assigned roles
775
+ // still exist in the database. We need to handle deleted roles.
776
+ t .Run ("RoleNotExists" , func (t * testing.T ) {
777
+ t .Parallel ()
778
+ var (
779
+ roleNotExistsName = "role-not-exists"
780
+ db = dbmem .New ()
781
+ org = dbgen .Organization (t , db , database.Organization {})
782
+ user = dbgen .User (t , db , database.User {
783
+ RBACRoles : []string {
784
+ // Also provide an org not exists. In practice this makes no sense
785
+ // to store org roles in the user table, but there is no org to
786
+ // store it in. So just throw this here for even more unexpected
787
+ // behavior handling!
788
+ rbac.RoleIdentifier {Name : roleNotExistsName , OrganizationID : uuid .New ()}.String (),
789
+ },
790
+ })
791
+ _ = dbgen .OrganizationMember (t , db , database.OrganizationMember {
792
+ UserID : user .ID ,
793
+ OrganizationID : org .ID ,
794
+ CreatedAt : time.Time {},
795
+ UpdatedAt : time.Time {},
796
+ Roles : []string {
797
+ rbac .RoleOrgAdmin (),
798
+ roleNotExistsName ,
799
+ },
800
+ })
801
+ _ , token = dbgen .APIKey (t , db , database.APIKey {
802
+ UserID : user .ID ,
803
+ ExpiresAt : dbtime .Now ().AddDate (0 , 0 , 1 ),
804
+ })
805
+
806
+ r = httptest .NewRequest ("GET" , "/" , nil )
807
+ rw = httptest .NewRecorder ()
808
+ )
809
+ r .Header .Set (codersdk .SessionTokenHeader , token )
810
+
811
+ httpmw .ExtractAPIKeyMW (httpmw.ExtractAPIKeyConfig {
812
+ DB : db ,
813
+ RedirectToLogin : false ,
814
+ })(http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
815
+ assertActorOk (t , r )
816
+ auth := httpmw .UserAuthorization (r )
817
+
818
+ roles , err := auth .Roles .Expand ()
819
+ assert .NoError (t , err , "expand user roles" )
820
+ // Assert built in org role
821
+ assert .True (t , slices .ContainsFunc (roles , func (role rbac.Role ) bool {
822
+ return role .Identifier .Name == rbac .RoleOrgAdmin () && role .Identifier .OrganizationID == org .ID
823
+ }), "org admin role" )
824
+
825
+ // Assert the role-not-exists is not returned
826
+ assert .False (t , slices .ContainsFunc (roles , func (role rbac.Role ) bool {
827
+ return role .Identifier .Name == roleNotExistsName
828
+ }), "role should not exist" )
829
+
830
+ httpapi .Write (r .Context (), rw , http .StatusOK , codersdk.Response {
831
+ Message : "It worked!" ,
832
+ })
833
+ })).ServeHTTP (rw , r )
834
+ res := rw .Result ()
835
+ defer res .Body .Close ()
836
+ require .Equal (t , http .StatusOK , res .StatusCode )
837
+ })
670
838
}
0 commit comments