feat: add API key scopes and application_connect scope (#4067)

This commit is contained in:
Dean Sheather
2022-09-19 17:39:02 +00:00
committed by GitHub
parent adad347902
commit 29d804e692
42 changed files with 476 additions and 88 deletions
+21 -8
View File
@@ -35,16 +35,23 @@ func APIKey(r *http.Request) database.APIKey {
}
// User roles are the 'subject' field of Authorize()
type userRolesKey struct{}
type userAuthKey struct{}
// AuthorizationUserRoles returns the roles used for authorization.
// Comes from the ExtractAPIKey handler.
func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesRow {
userRoles, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
type Authorization struct {
ID uuid.UUID
Username string
Roles []string
Scope database.APIKeyScope
}
// UserAuthorization returns the roles and scope used for authorization. Depends
// on the ExtractAPIKey handler.
func UserAuthorization(r *http.Request) Authorization {
auth, ok := r.Context().Value(userAuthKey{}).(Authorization)
if !ok {
panic("developer error: user roles middleware not provided")
panic("developer error: ExtractAPIKey middleware not provided")
}
return userRoles
return auth
}
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
@@ -324,7 +331,13 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool
ctx := r.Context()
ctx = context.WithValue(ctx, apiKeyContextKey{}, key)
ctx = context.WithValue(ctx, userRolesKey{}, roles)
ctx = context.WithValue(ctx, userAuthKey{}, Authorization{
ID: key.UserID,
Username: roles.Username,
Roles: roles.Roles,
Scope: key.Scope,
})
next.ServeHTTP(rw, r.WithContext(ctx))
})
}
+50
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
@@ -145,6 +146,7 @@ func TestAPIKey(t *testing.T) {
ID: id,
HashedSecret: hashed[:],
UserID: user.ID,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r)
@@ -170,6 +172,7 @@ func TestAPIKey(t *testing.T) {
HashedSecret: hashed[:],
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r)
@@ -196,6 +199,7 @@ func TestAPIKey(t *testing.T) {
ExpiresAt: database.Now().AddDate(0, 0, 1),
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@@ -215,6 +219,46 @@ func TestAPIKey(t *testing.T) {
require.Equal(t, sentAPIKey.ExpiresAt, gotAPIKey.ExpiresAt)
})
t.Run("ValidWithScope", func(t *testing.T) {
t.Parallel()
var (
db = databasefake.New()
id, secret = randomAPIKeyParts()
hashed = sha256.Sum256([]byte(secret))
r = httptest.NewRequest("GET", "/", nil)
rw = httptest.NewRecorder()
user = createUser(r.Context(), t, db)
)
r.AddCookie(&http.Cookie{
Name: codersdk.SessionTokenKey,
Value: fmt.Sprintf("%s-%s", id, secret),
})
_, err := db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
ID: id,
UserID: user.ID,
HashedSecret: hashed[:],
ExpiresAt: database.Now().AddDate(0, 0, 1),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeApplicationConnect,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Checks that it exists on the context!
apiKey := httpmw.APIKey(r)
assert.Equal(t, database.APIKeyScopeApplicationConnect, apiKey.Scope)
httpapi.Write(rw, http.StatusOK, codersdk.Response{
Message: "it worked!",
})
})).ServeHTTP(rw, r)
res := rw.Result()
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
})
t.Run("QueryParameter", func(t *testing.T) {
t.Parallel()
var (
@@ -235,6 +279,7 @@ func TestAPIKey(t *testing.T) {
ExpiresAt: database.Now().AddDate(0, 0, 1),
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@@ -268,6 +313,7 @@ func TestAPIKey(t *testing.T) {
ExpiresAt: database.Now().AddDate(0, 0, 1),
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r)
@@ -301,6 +347,7 @@ func TestAPIKey(t *testing.T) {
ExpiresAt: database.Now().Add(time.Minute),
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r)
@@ -334,6 +381,7 @@ func TestAPIKey(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().AddDate(0, 0, 1),
UserID: user.ID,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
@@ -373,6 +421,7 @@ func TestAPIKey(t *testing.T) {
LoginType: database.LoginTypeGithub,
LastUsed: database.Now(),
UserID: user.ID,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
_, err = db.InsertUserLink(r.Context(), database.InsertUserLinkParams{
@@ -425,6 +474,7 @@ func TestAPIKey(t *testing.T) {
ExpiresAt: database.Now().AddDate(0, 0, 1),
UserID: user.ID,
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
httpmw.ExtractAPIKey(db, nil, false)(successHandler).ServeHTTP(rw, r)
+2 -1
View File
@@ -87,7 +87,7 @@ func TestExtractUserRoles(t *testing.T) {
httpmw.ExtractAPIKey(db, &httpmw.OAuth2Configs{}, false),
)
rtr.Get("/", func(_ http.ResponseWriter, r *http.Request) {
roles := httpmw.AuthorizationUserRoles(r)
roles := httpmw.UserAuthorization(r)
require.ElementsMatch(t, user.ID, roles.ID)
require.ElementsMatch(t, expRoles, roles.Roles)
})
@@ -124,6 +124,7 @@ func addUser(t *testing.T, db database.Store, roles ...string) (database.User, s
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
+1
View File
@@ -51,6 +51,7 @@ func TestOrganizationParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, chi.NewRouteContext()))
+1
View File
@@ -51,6 +51,7 @@ func TestTemplateParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
@@ -51,6 +51,7 @@ func TestTemplateVersionParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
+1
View File
@@ -45,6 +45,7 @@ func TestUserParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
@@ -51,6 +51,7 @@ func TestWorkspaceAgentParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
@@ -51,6 +51,7 @@ func TestWorkspaceBuildParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
+2
View File
@@ -54,6 +54,7 @@ func TestWorkspaceParam(t *testing.T) {
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)
@@ -359,6 +360,7 @@ func setupWorkspaceWithAgents(t testing.TB, cfg setupConfig) (database.Store, *h
LastUsed: database.Now(),
ExpiresAt: database.Now().Add(time.Minute),
LoginType: database.LoginTypePassword,
Scope: database.APIKeyScopeAll,
})
require.NoError(t, err)