feat: Implied 'member' roles for site and organization (#1917)

* feat: Member roles are implied and never exlpicitly added
* Rename "GetAllUserRoles" to "GetAuthorizationRoles"
* feat: Add migration to remove implied roles
* rename user auth role middleware
This commit is contained in:
Steven Masley
2022-06-01 09:07:50 -05:00
committed by GitHub
parent 2878346f19
commit cc87a0cf6b
21 changed files with 131 additions and 115 deletions

View File

@ -31,6 +31,19 @@ func APIKey(r *http.Request) database.APIKey {
return apiKey
}
// User roles are the 'subject' field of Authorize()
type userRolesKey struct{}
// AuthorizationUserRoles returns the roles used for authorization.
// Comes from the ExtractAPIKey handler.
func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesRow {
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
if !ok {
panic("developer error: user roles middleware not provided")
}
return apiKey
}
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
// This should be extended to support other authentication types in the future.
type OAuth2Configs struct {
@ -178,7 +191,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs) func(http.Handler) h
// If the key is valid, we also fetch the user roles and status.
// The roles are used for RBAC authorize checks, and the status
// is to block 'suspended' users from accessing the platform.
roles, err := db.GetAllUserRoles(r.Context(), key.UserID)
roles, err := db.GetAuthorizationUserRoles(r.Context(), key.UserID)
if err != nil {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "roles not found",

View File

@ -1,40 +0,0 @@
package httpmw
import (
"context"
"net/http"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
)
// User roles are the 'subject' field of Authorize()
type userRolesKey struct{}
// UserRoles returns the API key from the ExtractUserRoles handler.
func UserRoles(r *http.Request) database.GetAllUserRolesRow {
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAllUserRolesRow)
if !ok {
panic("developer error: user roles middleware not provided")
}
return apiKey
}
// ExtractUserRoles requires authentication using a valid API key.
func ExtractUserRoles(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
apiKey := APIKey(r)
role, err := db.GetAllUserRoles(r.Context(), apiKey.UserID)
if err != nil {
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
Message: "roles not found",
})
return
}
ctx := context.WithValue(r.Context(), userRolesKey{}, role)
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
}

View File

@ -31,23 +31,23 @@ func TestExtractUserRoles(t *testing.T) {
{
Name: "Member",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember()}
roles := []string{}
user, token := addUser(t, db, roles...)
return user, roles, token
return user, append(roles, rbac.RoleMember()), token
},
},
{
Name: "Admin",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember(), rbac.RoleAdmin()}
roles := []string{rbac.RoleAdmin()}
user, token := addUser(t, db, roles...)
return user, roles, token
return user, append(roles, rbac.RoleMember()), token
},
},
{
Name: "OrgMember",
AddUser: func(db database.Store) (database.User, []string, string) {
roles := []string{rbac.RoleMember()}
roles := []string{}
user, token := addUser(t, db, roles...)
org, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{
ID: uuid.New(),
@ -58,7 +58,7 @@ func TestExtractUserRoles(t *testing.T) {
})
require.NoError(t, err)
orgRoles := []string{rbac.RoleOrgMember(org.ID)}
orgRoles := []string{}
_, err = db.InsertOrganizationMember(context.Background(), database.InsertOrganizationMemberParams{
OrganizationID: org.ID,
UserID: user.ID,
@ -67,7 +67,7 @@ func TestExtractUserRoles(t *testing.T) {
Roles: orgRoles,
})
require.NoError(t, err)
return user, append(roles, orgRoles...), token
return user, append(roles, append(orgRoles, rbac.RoleMember(), rbac.RoleOrgMember(org.ID))...), token
},
},
}
@ -86,7 +86,7 @@ func TestExtractUserRoles(t *testing.T) {
httpmw.ExtractAPIKey(db, &httpmw.OAuth2Configs{}),
)
rtr.Get("/", func(_ http.ResponseWriter, r *http.Request) {
roles := httpmw.UserRoles(r)
roles := httpmw.AuthorizationUserRoles(r)
require.ElementsMatch(t, user.ID, roles.ID)
require.ElementsMatch(t, expRoles, roles.Roles)
})