feat: add oauth2 token exchange (#12196)

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
This commit is contained in:
Asher
2024-02-20 14:58:43 -09:00
committed by GitHub
co-authored by Steven Masley
parent 07cccf9033
commit 4d39da294e
46 changed files with 4008 additions and 167 deletions
+169
View File
@@ -1491,6 +1491,146 @@ const docTemplate = `{
}
}
},
"/login/oauth2/authorize": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Enterprise"
],
"summary": "OAuth2 authorization request.",
"operationId": "oauth2-authorization-request",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "client_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "A random unguessable string",
"name": "state",
"in": "query",
"required": true
},
{
"enum": [
"code"
],
"type": "string",
"description": "Response type",
"name": "response_type",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Redirect here after authorization",
"name": "redirect_uri",
"in": "query"
},
{
"type": "string",
"description": "Token scopes (currently ignored)",
"name": "scope",
"in": "query"
}
],
"responses": {
"302": {
"description": "Found"
}
}
}
},
"/login/oauth2/tokens": {
"post": {
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "OAuth2 token exchange.",
"operationId": "oauth2-token-exchange",
"parameters": [
{
"type": "string",
"description": "Client ID, required if grant_type=authorization_code",
"name": "client_id",
"in": "formData"
},
{
"type": "string",
"description": "Client secret, required if grant_type=authorization_code",
"name": "client_secret",
"in": "formData"
},
{
"type": "string",
"description": "Authorization code, required if grant_type=authorization_code",
"name": "code",
"in": "formData"
},
{
"type": "string",
"description": "Refresh token, required if grant_type=refresh_token",
"name": "refresh_token",
"in": "formData"
},
{
"enum": [
"authorization_code",
"refresh_token"
],
"type": "string",
"description": "Grant type",
"name": "grant_type",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/oauth2.Token"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Enterprise"
],
"summary": "Delete OAuth2 application tokens.",
"operationId": "delete-oauth2-application-tokens",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "client_id",
"in": "query",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/oauth2-provider/apps": {
"get": {
"security": [
@@ -1506,6 +1646,14 @@ const docTemplate = `{
],
"summary": "Get OAuth2 applications.",
"operationId": "get-oauth2-applications",
"parameters": [
{
"type": "string",
"description": "Filter by applications authorized for a user",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
@@ -13948,6 +14096,27 @@ const docTemplate = `{
}
}
},
"oauth2.Token": {
"type": "object",
"properties": {
"access_token": {
"description": "AccessToken is the token that authorizes and authenticates\nthe requests.",
"type": "string"
},
"expiry": {
"description": "Expiry is the optional expiration time of the access token.\n\nIf zero, TokenSource implementations will reuse the same\ntoken forever and RefreshToken or equivalent\nmechanisms for that TokenSource will not be used.",
"type": "string"
},
"refresh_token": {
"description": "RefreshToken is a token that's used by the application\n(as opposed to the user) to refresh the access token\nif it expires.",
"type": "string"
},
"token_type": {
"description": "TokenType is the type of token.\nThe Type method returns either this or \"Bearer\", the default.",
"type": "string"
}
}
},
"tailcfg.DERPHomeParams": {
"type": "object",
"properties": {
+156
View File
@@ -1291,6 +1291,133 @@
}
}
},
"/login/oauth2/authorize": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Enterprise"],
"summary": "OAuth2 authorization request.",
"operationId": "oauth2-authorization-request",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "client_id",
"in": "query",
"required": true
},
{
"type": "string",
"description": "A random unguessable string",
"name": "state",
"in": "query",
"required": true
},
{
"enum": ["code"],
"type": "string",
"description": "Response type",
"name": "response_type",
"in": "query",
"required": true
},
{
"type": "string",
"description": "Redirect here after authorization",
"name": "redirect_uri",
"in": "query"
},
{
"type": "string",
"description": "Token scopes (currently ignored)",
"name": "scope",
"in": "query"
}
],
"responses": {
"302": {
"description": "Found"
}
}
}
},
"/login/oauth2/tokens": {
"post": {
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "OAuth2 token exchange.",
"operationId": "oauth2-token-exchange",
"parameters": [
{
"type": "string",
"description": "Client ID, required if grant_type=authorization_code",
"name": "client_id",
"in": "formData"
},
{
"type": "string",
"description": "Client secret, required if grant_type=authorization_code",
"name": "client_secret",
"in": "formData"
},
{
"type": "string",
"description": "Authorization code, required if grant_type=authorization_code",
"name": "code",
"in": "formData"
},
{
"type": "string",
"description": "Refresh token, required if grant_type=refresh_token",
"name": "refresh_token",
"in": "formData"
},
{
"enum": ["authorization_code", "refresh_token"],
"type": "string",
"description": "Grant type",
"name": "grant_type",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/oauth2.Token"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Enterprise"],
"summary": "Delete OAuth2 application tokens.",
"operationId": "delete-oauth2-application-tokens",
"parameters": [
{
"type": "string",
"description": "Client ID",
"name": "client_id",
"in": "query",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/oauth2-provider/apps": {
"get": {
"security": [
@@ -1302,6 +1429,14 @@
"tags": ["Enterprise"],
"summary": "Get OAuth2 applications.",
"operationId": "get-oauth2-applications",
"parameters": [
{
"type": "string",
"description": "Filter by applications authorized for a user",
"name": "user_id",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
@@ -12716,6 +12851,27 @@
}
}
},
"oauth2.Token": {
"type": "object",
"properties": {
"access_token": {
"description": "AccessToken is the token that authorizes and authenticates\nthe requests.",
"type": "string"
},
"expiry": {
"description": "Expiry is the optional expiration time of the access token.\n\nIf zero, TokenSource implementations will reuse the same\ntoken forever and RefreshToken or equivalent\nmechanisms for that TokenSource will not be used.",
"type": "string"
},
"refresh_token": {
"description": "RefreshToken is a token that's used by the application\n(as opposed to the user) to refresh the access token\nif it expires.",
"type": "string"
},
"token_type": {
"description": "TokenType is the type of token.\nThe Type method returns either this or \"Bearer\", the default.",
"type": "string"
}
}
},
"tailcfg.DERPHomeParams": {
"type": "object",
"properties": {
+52
View File
@@ -1,14 +1,17 @@
package oidctest
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"net/url"
"testing"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -114,3 +117,52 @@ func (h *LoginHelper) ForceRefresh(t *testing.T, db database.Store, user *coders
_, err := user.User(testutil.Context(t, testutil.WaitShort), "me")
require.NoError(t, err, "user must be able to be fetched")
}
// OAuth2GetCode emulates a user clicking "allow" on the IDP page. When doing
// unit tests, it's easier to skip this step sometimes. It does make an actual
// request to the IDP, so it should be equivalent to doing this "manually" with
// actual requests.
func OAuth2GetCode(rawAuthURL string, doRequest func(req *http.Request) (*http.Response, error)) (string, error) {
authURL, err := url.Parse(rawAuthURL)
if err != nil {
return "", xerrors.Errorf("failed to parse auth URL: %w", err)
}
r, err := http.NewRequestWithContext(context.Background(), http.MethodGet, rawAuthURL, nil)
if err != nil {
return "", xerrors.Errorf("failed to create auth request: %w", err)
}
expCode := http.StatusTemporaryRedirect
resp, err := doRequest(r)
if err != nil {
return "", xerrors.Errorf("request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != expCode {
return "", codersdk.ReadBodyAsError(resp)
}
to := resp.Header.Get("Location")
if to == "" {
return "", xerrors.Errorf("expected redirect location")
}
toURL, err := url.Parse(to)
if err != nil {
return "", xerrors.Errorf("failed to parse redirect location: %w", err)
}
code := toURL.Query().Get("code")
if code == "" {
return "", xerrors.Errorf("expected code in redirect location")
}
state := authURL.Query().Get("state")
newState := toURL.Query().Get("state")
if newState != state {
return "", xerrors.Errorf("expected state %q, got %q", state, newState)
}
return code, nil
}
+9 -28
View File
@@ -534,37 +534,18 @@ func (*FakeIDP) DeviceLogin(t testing.TB, client *codersdk.Client, externalAuthI
// unit tests, it's easier to skip this step sometimes. It does make an actual
// request to the IDP, so it should be equivalent to doing this "manually" with
// actual requests.
func (f *FakeIDP) CreateAuthCode(t testing.TB, state string, opts ...func(r *http.Request)) string {
func (f *FakeIDP) CreateAuthCode(t testing.TB, state string) string {
// We need to store some claims, because this is also an OIDC provider, and
// it expects some claims to be present.
f.stateToIDTokenClaims.Store(state, jwt.MapClaims{})
u := f.cfg.AuthCodeURL(state)
r, err := http.NewRequestWithContext(context.Background(), http.MethodPost, u, nil)
require.NoError(t, err, "failed to create auth request")
for _, opt := range opts {
opt(r)
}
rw := httptest.NewRecorder()
f.handler.ServeHTTP(rw, r)
resp := rw.Result()
defer resp.Body.Close()
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode, "expected redirect")
to := resp.Header.Get("Location")
require.NotEmpty(t, to, "expected redirect location")
toURL, err := url.Parse(to)
require.NoError(t, err, "failed to parse redirect location")
code := toURL.Query().Get("code")
require.NotEmpty(t, code, "expected code in redirect location")
newState := toURL.Query().Get("state")
require.Equal(t, state, newState, "expected state to match")
code, err := OAuth2GetCode(f.cfg.AuthCodeURL(state), func(req *http.Request) (*http.Response, error) {
rw := httptest.NewRecorder()
f.handler.ServeHTTP(rw, req)
resp := rw.Result()
return resp, nil
})
require.NoError(t, err, "failed to get auth code")
return code
}
@@ -1071,7 +1052,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
f.logger.Info(r.Context(), "http call device auth")
p := httpapi.NewQueryParamParser()
p.Required("client_id")
p.RequiredNotEmpty("client_id")
clientID := p.String(r.URL.Query(), "", "client_id")
_ = p.String(r.URL.Query(), "", "scopes")
if len(p.Errors) > 0 {
+83
View File
@@ -801,6 +801,25 @@ func (q *querier) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID)
return q.db.DeleteOAuth2ProviderAppByID(ctx, id)
}
func (q *querier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error {
code, err := q.db.GetOAuth2ProviderAppCodeByID(ctx, id)
if err != nil {
return err
}
if err := q.authorizeContext(ctx, rbac.ActionDelete, code); err != nil {
return err
}
return q.db.DeleteOAuth2ProviderAppCodeByID(ctx, id)
}
func (q *querier) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
if err := q.authorizeContext(ctx, rbac.ActionDelete,
rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil {
return err
}
return q.db.DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx, arg)
}
func (q *querier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error {
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2ProviderAppSecret); err != nil {
return err
@@ -808,6 +827,14 @@ func (q *querier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid
return q.db.DeleteOAuth2ProviderAppSecretByID(ctx, id)
}
func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error {
if err := q.authorizeContext(ctx, rbac.ActionDelete,
rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil {
return err
}
return q.db.DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx, arg)
}
func (q *querier) DeleteOldProvisionerDaemons(ctx context.Context) error {
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil {
return err
@@ -1175,6 +1202,14 @@ func (q *querier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (d
return q.db.GetOAuth2ProviderAppByID(ctx, id)
}
func (q *querier) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) {
return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppCodeByID)(ctx, id)
}
func (q *querier) GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (database.OAuth2ProviderAppCode, error) {
return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppCodeByPrefix)(ctx, secretPrefix)
}
func (q *querier) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderAppSecret); err != nil {
return database.OAuth2ProviderAppSecret{}, err
@@ -1182,6 +1217,10 @@ func (q *querier) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UU
return q.db.GetOAuth2ProviderAppSecretByID(ctx, id)
}
func (q *querier) GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (database.OAuth2ProviderAppSecret, error) {
return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppSecretByPrefix)(ctx, secretPrefix)
}
func (q *querier) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]database.OAuth2ProviderAppSecret, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderAppSecret); err != nil {
return []database.OAuth2ProviderAppSecret{}, err
@@ -1189,6 +1228,22 @@ func (q *querier) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID
return q.db.GetOAuth2ProviderAppSecretsByAppID(ctx, appID)
}
func (q *querier) GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (database.OAuth2ProviderAppToken, error) {
token, err := q.db.GetOAuth2ProviderAppTokenByPrefix(ctx, hashPrefix)
if err != nil {
return database.OAuth2ProviderAppToken{}, err
}
// The user ID is on the API key so that has to be fetched.
key, err := q.db.GetAPIKeyByID(ctx, token.APIKeyID)
if err != nil {
return database.OAuth2ProviderAppToken{}, err
}
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(key.UserID.String())); err != nil {
return database.OAuth2ProviderAppToken{}, err
}
return token, nil
}
func (q *querier) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAuth2ProviderApp, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderApp); err != nil {
return []database.OAuth2ProviderApp{}, err
@@ -1196,6 +1251,15 @@ func (q *querier) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAuth2P
return q.db.GetOAuth2ProviderApps(ctx)
}
func (q *querier) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) {
// This authz check is to make sure the caller can read all their own tokens.
if err := q.authorizeContext(ctx, rbac.ActionRead,
rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(userID.String())); err != nil {
return []database.GetOAuth2ProviderAppsByUserIDRow{}, err
}
return q.db.GetOAuth2ProviderAppsByUserID(ctx, userID)
}
func (q *querier) GetOAuthSigningKey(ctx context.Context) (string, error) {
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
return "", err
@@ -2242,6 +2306,14 @@ func (q *querier) InsertOAuth2ProviderApp(ctx context.Context, arg database.Inse
return q.db.InsertOAuth2ProviderApp(ctx, arg)
}
func (q *querier) InsertOAuth2ProviderAppCode(ctx context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) {
if err := q.authorizeContext(ctx, rbac.ActionCreate,
rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil {
return database.OAuth2ProviderAppCode{}, err
}
return q.db.InsertOAuth2ProviderAppCode(ctx, arg)
}
func (q *querier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) {
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2ProviderAppSecret); err != nil {
return database.OAuth2ProviderAppSecret{}, err
@@ -2249,6 +2321,17 @@ func (q *querier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg databas
return q.db.InsertOAuth2ProviderAppSecret(ctx, arg)
}
func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) {
key, err := q.db.GetAPIKeyByID(ctx, arg.APIKeyID)
if err != nil {
return database.OAuth2ProviderAppToken{}, err
}
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(key.UserID.String())); err != nil {
return database.OAuth2ProviderAppToken{}, err
}
return q.db.InsertOAuth2ProviderAppToken(ctx, arg)
}
func (q *querier) InsertOrganization(ctx context.Context, arg database.InsertOrganizationParams) (database.Organization, error) {
return insert(q.log, q.auth, rbac.ResourceOrganization, q.db.InsertOrganization)(ctx, arg)
}
+139
View File
@@ -2316,6 +2316,34 @@ func (s *MethodTestSuite) TestOAuth2ProviderApps() {
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
check.Args(app.ID).Asserts(rbac.ResourceOAuth2ProviderApp, rbac.ActionRead).Returns(app)
}))
s.Run("GetOAuth2ProviderAppsByUserID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
key, _ := dbgen.APIKey(s.T(), db, database.APIKey{
UserID: user.ID,
})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
_ = dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})
for i := 0; i < 5; i++ {
_ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
APIKeyID: key.ID,
})
}
check.Args(user.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns([]database.GetOAuth2ProviderAppsByUserIDRow{
{
OAuth2ProviderApp: database.OAuth2ProviderApp{
ID: app.ID,
CallbackURL: app.CallbackURL,
Icon: app.Icon,
Name: app.Name,
},
TokenCount: 5,
},
})
}))
s.Run("InsertOAuth2ProviderApp", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.InsertOAuth2ProviderAppParams{}).Asserts(rbac.ResourceOAuth2ProviderApp, rbac.ActionCreate)
}))
@@ -2361,6 +2389,13 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppSecrets() {
})
check.Args(secret.ID).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionRead).Returns(secret)
}))
s.Run("GetOAuth2ProviderAppSecretByPrefix", s.Subtest(func(db database.Store, check *expects) {
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})
check.Args(secret.SecretPrefix).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionRead).Returns(secret)
}))
s.Run("InsertOAuth2ProviderAppSecret", s.Subtest(func(db database.Store, check *expects) {
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
check.Args(database.InsertOAuth2ProviderAppSecretParams{
@@ -2386,3 +2421,107 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppSecrets() {
check.Args(secret.ID).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionDelete)
}))
}
func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() {
s.Run("GetOAuth2ProviderAppCodeByID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{
AppID: app.ID,
UserID: user.ID,
})
check.Args(code.ID).Asserts(code, rbac.ActionRead).Returns(code)
}))
s.Run("GetOAuth2ProviderAppCodeByPrefix", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{
AppID: app.ID,
UserID: user.ID,
})
check.Args(code.SecretPrefix).Asserts(code, rbac.ActionRead).Returns(code)
}))
s.Run("InsertOAuth2ProviderAppCode", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
check.Args(database.InsertOAuth2ProviderAppCodeParams{
AppID: app.ID,
UserID: user.ID,
}).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionCreate)
}))
s.Run("DeleteOAuth2ProviderAppCodeByID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{
AppID: app.ID,
UserID: user.ID,
})
check.Args(code.ID).Asserts(code, rbac.ActionDelete)
}))
s.Run("DeleteOAuth2ProviderAppCodesByAppAndUserID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
for i := 0; i < 5; i++ {
_ = dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{
AppID: app.ID,
UserID: user.ID,
})
}
check.Args(database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams{
AppID: app.ID,
UserID: user.ID,
}).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete)
}))
}
func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() {
s.Run("InsertOAuth2ProviderAppToken", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
key, _ := dbgen.APIKey(s.T(), db, database.APIKey{
UserID: user.ID,
})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})
check.Args(database.InsertOAuth2ProviderAppTokenParams{
AppSecretID: secret.ID,
APIKeyID: key.ID,
}).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionCreate)
}))
s.Run("GetOAuth2ProviderAppTokenByPrefix", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
key, _ := dbgen.APIKey(s.T(), db, database.APIKey{
UserID: user.ID,
})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})
token := dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
APIKeyID: key.ID,
})
check.Args(token.HashPrefix).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead)
}))
s.Run("DeleteOAuth2ProviderAppTokensByAppAndUserID", s.Subtest(func(db database.Store, check *expects) {
user := dbgen.User(s.T(), db, database.User{})
key, _ := dbgen.APIKey(s.T(), db, database.APIKey{
UserID: user.ID,
})
app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{})
secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{
AppID: app.ID,
})
for i := 0; i < 5; i++ {
_ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{
AppSecretID: secret.ID,
APIKeyID: key.ID,
})
}
check.Args(database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams{
AppID: app.ID,
UserID: user.ID,
}).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete)
}))
}
+29
View File
@@ -707,6 +707,7 @@ func OAuth2ProviderAppSecret(t testing.TB, db database.Store, seed database.OAut
app, err := db.InsertOAuth2ProviderAppSecret(genCtx, database.InsertOAuth2ProviderAppSecretParams{
ID: takeFirst(seed.ID, uuid.New()),
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
SecretPrefix: takeFirstSlice(seed.SecretPrefix, []byte("prefix")),
HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")),
DisplaySecret: takeFirst(seed.DisplaySecret, "secret"),
AppID: takeFirst(seed.AppID, uuid.New()),
@@ -715,6 +716,34 @@ func OAuth2ProviderAppSecret(t testing.TB, db database.Store, seed database.OAut
return app
}
func OAuth2ProviderAppCode(t testing.TB, db database.Store, seed database.OAuth2ProviderAppCode) database.OAuth2ProviderAppCode {
code, err := db.InsertOAuth2ProviderAppCode(genCtx, database.InsertOAuth2ProviderAppCodeParams{
ID: takeFirst(seed.ID, uuid.New()),
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()),
SecretPrefix: takeFirstSlice(seed.SecretPrefix, []byte("prefix")),
HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")),
AppID: takeFirst(seed.AppID, uuid.New()),
UserID: takeFirst(seed.UserID, uuid.New()),
})
require.NoError(t, err, "insert oauth2 app code")
return code
}
func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth2ProviderAppToken) database.OAuth2ProviderAppToken {
token, err := db.InsertOAuth2ProviderAppToken(genCtx, database.InsertOAuth2ProviderAppTokenParams{
ID: takeFirst(seed.ID, uuid.New()),
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()),
HashPrefix: takeFirstSlice(seed.HashPrefix, []byte("prefix")),
RefreshHash: takeFirstSlice(seed.RefreshHash, []byte("hashed-secret")),
AppSecretID: takeFirst(seed.AppSecretID, uuid.New()),
APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()),
})
require.NoError(t, err, "insert oauth2 app token")
return token
}
func must[V any](v V, err error) V {
if err != nil {
panic(err)
+271 -18
View File
@@ -1,6 +1,7 @@
package dbmem
import (
"bytes"
"context"
"database/sql"
"encoding/json"
@@ -133,6 +134,8 @@ type data struct {
licenses []database.License
oauth2ProviderApps []database.OAuth2ProviderApp
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
oauth2ProviderAppTokens []database.OAuth2ProviderAppToken
parameterSchemas []database.ParameterSchema
provisionerDaemons []database.ProvisionerDaemon
provisionerJobLogs []database.ProvisionerJobLog
@@ -1165,19 +1168,72 @@ func (q *FakeQuerier) DeleteOAuth2ProviderAppByID(_ context.Context, id uuid.UUI
q.mutex.Lock()
defer q.mutex.Unlock()
for index, app := range q.oauth2ProviderApps {
if app.ID == id {
q.oauth2ProviderApps[index] = q.oauth2ProviderApps[len(q.oauth2ProviderApps)-1]
q.oauth2ProviderApps = q.oauth2ProviderApps[:len(q.oauth2ProviderApps)-1]
index := slices.IndexFunc(q.oauth2ProviderApps, func(app database.OAuth2ProviderApp) bool {
return app.ID == id
})
secrets := []database.OAuth2ProviderAppSecret{}
for _, secret := range q.oauth2ProviderAppSecrets {
if secret.AppID != id {
secrets = append(secrets, secret)
}
}
q.oauth2ProviderAppSecrets = secrets
if index < 0 {
return sql.ErrNoRows
}
q.oauth2ProviderApps[index] = q.oauth2ProviderApps[len(q.oauth2ProviderApps)-1]
q.oauth2ProviderApps = q.oauth2ProviderApps[:len(q.oauth2ProviderApps)-1]
// Cascade delete secrets associated with the deleted app.
var deletedSecretIDs []uuid.UUID
q.oauth2ProviderAppSecrets = slices.DeleteFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool {
matches := secret.AppID == id
if matches {
deletedSecretIDs = append(deletedSecretIDs, secret.ID)
}
return matches
})
// Cascade delete tokens through the deleted secrets.
var keyIDsToDelete []string
q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool {
matches := slice.Contains(deletedSecretIDs, token.AppSecretID)
if matches {
keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID)
}
return matches
})
// Cascade delete API keys linked to the deleted tokens.
q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool {
return slices.Contains(keyIDsToDelete, key.ID)
})
return nil
}
func (q *FakeQuerier) DeleteOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) error {
q.mutex.Lock()
defer q.mutex.Unlock()
for index, code := range q.oauth2ProviderAppCodes {
if code.ID == id {
q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1]
q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1]
return nil
}
}
return sql.ErrNoRows
}
func (q *FakeQuerier) DeleteOAuth2ProviderAppCodesByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for index, code := range q.oauth2ProviderAppCodes {
if code.AppID == arg.AppID && code.UserID == arg.UserID {
q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1]
q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1]
return nil
}
}
@@ -1188,14 +1244,68 @@ func (q *FakeQuerier) DeleteOAuth2ProviderAppSecretByID(_ context.Context, id uu
q.mutex.Lock()
defer q.mutex.Unlock()
for index, secret := range q.oauth2ProviderAppSecrets {
if secret.ID == id {
q.oauth2ProviderAppSecrets[index] = q.oauth2ProviderAppSecrets[len(q.oauth2ProviderAppSecrets)-1]
q.oauth2ProviderAppSecrets = q.oauth2ProviderAppSecrets[:len(q.oauth2ProviderAppSecrets)-1]
return nil
}
index := slices.IndexFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool {
return secret.ID == id
})
if index < 0 {
return sql.ErrNoRows
}
return sql.ErrNoRows
q.oauth2ProviderAppSecrets[index] = q.oauth2ProviderAppSecrets[len(q.oauth2ProviderAppSecrets)-1]
q.oauth2ProviderAppSecrets = q.oauth2ProviderAppSecrets[:len(q.oauth2ProviderAppSecrets)-1]
// Cascade delete tokens created through the deleted secret.
var keyIDsToDelete []string
q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool {
matches := token.AppSecretID == id
if matches {
keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID)
}
return matches
})
// Cascade delete API keys linked to the deleted tokens.
q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool {
return slices.Contains(keyIDsToDelete, key.ID)
})
return nil
}
func (q *FakeQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
var keyIDsToDelete []string
q.oauth2ProviderAppTokens = slices.DeleteFunc(q.oauth2ProviderAppTokens, func(token database.OAuth2ProviderAppToken) bool {
// Join secrets and keys to see if the token matches.
secretIdx := slices.IndexFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool {
return secret.ID == token.AppSecretID
})
keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool {
return key.ID == token.APIKeyID
})
matches := secretIdx != -1 &&
q.oauth2ProviderAppSecrets[secretIdx].AppID == arg.AppID &&
keyIdx != -1 && q.apiKeys[keyIdx].UserID == arg.UserID
if matches {
keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID)
}
return matches
})
// Cascade delete API keys linked to the deleted tokens.
q.apiKeys = slices.DeleteFunc(q.apiKeys, func(key database.APIKey) bool {
return slices.Contains(keyIDsToDelete, key.ID)
})
return nil
}
func (q *FakeQuerier) DeleteOldProvisionerDaemons(_ context.Context) error {
@@ -2138,6 +2248,30 @@ func (q *FakeQuerier) GetOAuth2ProviderAppByID(_ context.Context, id uuid.UUID)
return database.OAuth2ProviderApp{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
for _, code := range q.oauth2ProviderAppCodes {
if code.ID == id {
return code, nil
}
}
return database.OAuth2ProviderAppCode{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppCodeByPrefix(_ context.Context, secretPrefix []byte) (database.OAuth2ProviderAppCode, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
for _, code := range q.oauth2ProviderAppCodes {
if bytes.Equal(code.SecretPrefix, secretPrefix) {
return code, nil
}
}
return database.OAuth2ProviderAppCode{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppSecretByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
@@ -2150,6 +2284,18 @@ func (q *FakeQuerier) GetOAuth2ProviderAppSecretByID(_ context.Context, id uuid.
return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppSecretByPrefix(_ context.Context, secretPrefix []byte) (database.OAuth2ProviderAppSecret, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
for _, secret := range q.oauth2ProviderAppSecrets {
if bytes.Equal(secret.SecretPrefix, secretPrefix) {
return secret, nil
}
}
return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppSecretsByAppID(_ context.Context, appID uuid.UUID) ([]database.OAuth2ProviderAppSecret, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
@@ -2178,6 +2324,18 @@ func (q *FakeQuerier) GetOAuth2ProviderAppSecretsByAppID(_ context.Context, appI
return []database.OAuth2ProviderAppSecret{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderAppTokenByPrefix(_ context.Context, hashPrefix []byte) (database.OAuth2ProviderAppToken, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
for _, token := range q.oauth2ProviderAppTokens {
if bytes.Equal(token.HashPrefix, hashPrefix) {
return token, nil
}
}
return database.OAuth2ProviderAppToken{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetOAuth2ProviderApps(_ context.Context) ([]database.OAuth2ProviderApp, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
@@ -2188,6 +2346,42 @@ func (q *FakeQuerier) GetOAuth2ProviderApps(_ context.Context) ([]database.OAuth
return q.oauth2ProviderApps, nil
}
func (q *FakeQuerier) GetOAuth2ProviderAppsByUserID(_ context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
rows := []database.GetOAuth2ProviderAppsByUserIDRow{}
for _, app := range q.oauth2ProviderApps {
tokens := []database.OAuth2ProviderAppToken{}
for _, secret := range q.oauth2ProviderAppSecrets {
if secret.AppID == app.ID {
for _, token := range q.oauth2ProviderAppTokens {
if token.AppSecretID == secret.ID {
keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool {
return key.ID == token.APIKeyID
})
if keyIdx != -1 && q.apiKeys[keyIdx].UserID == userID {
tokens = append(tokens, token)
}
}
}
}
}
if len(tokens) > 0 {
rows = append(rows, database.GetOAuth2ProviderAppsByUserIDRow{
OAuth2ProviderApp: database.OAuth2ProviderApp{
CallbackURL: app.CallbackURL,
ID: app.ID,
Icon: app.Icon,
Name: app.Name,
},
TokenCount: int64(len(tokens)),
})
}
}
return rows, nil
}
func (q *FakeQuerier) GetOAuthSigningKey(_ context.Context) (string, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
@@ -5240,6 +5434,34 @@ func (q *FakeQuerier) InsertOAuth2ProviderApp(_ context.Context, arg database.In
return app, nil
}
func (q *FakeQuerier) InsertOAuth2ProviderAppCode(_ context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.OAuth2ProviderAppCode{}, err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for _, app := range q.oauth2ProviderApps {
if app.ID == arg.AppID {
code := database.OAuth2ProviderAppCode{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
ExpiresAt: arg.ExpiresAt,
SecretPrefix: arg.SecretPrefix,
HashedSecret: arg.HashedSecret,
UserID: arg.UserID,
AppID: arg.AppID,
}
q.oauth2ProviderAppCodes = append(q.oauth2ProviderAppCodes, code)
return code, nil
}
}
return database.OAuth2ProviderAppCode{}, sql.ErrNoRows
}
func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) {
err := validateDatabaseType(arg)
if err != nil {
@@ -5254,6 +5476,7 @@ func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg datab
secret := database.OAuth2ProviderAppSecret{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
SecretPrefix: arg.SecretPrefix,
HashedSecret: arg.HashedSecret,
DisplaySecret: arg.DisplaySecret,
AppID: arg.AppID,
@@ -5266,6 +5489,35 @@ func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg datab
return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows
}
func (q *FakeQuerier) InsertOAuth2ProviderAppToken(_ context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.OAuth2ProviderAppToken{}, err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for _, secret := range q.oauth2ProviderAppSecrets {
if secret.ID == arg.AppSecretID {
//nolint:gosimple // Go wants database.OAuth2ProviderAppToken(arg), but we cannot be sure the structs will remain identical.
token := database.OAuth2ProviderAppToken{
ID: arg.ID,
CreatedAt: arg.CreatedAt,
ExpiresAt: arg.ExpiresAt,
HashPrefix: arg.HashPrefix,
RefreshHash: arg.RefreshHash,
APIKeyID: arg.APIKeyID,
AppSecretID: arg.AppSecretID,
}
q.oauth2ProviderAppTokens = append(q.oauth2ProviderAppTokens, token)
return token, nil
}
}
return database.OAuth2ProviderAppToken{}, sql.ErrNoRows
}
func (q *FakeQuerier) InsertOrganization(_ context.Context, arg database.InsertOrganizationParams) (database.Organization, error) {
if err := validateDatabaseType(arg); err != nil {
return database.Organization{}, err
@@ -6372,6 +6624,7 @@ func (q *FakeQuerier) UpdateOAuth2ProviderAppSecretByID(_ context.Context, arg d
newSecret := database.OAuth2ProviderAppSecret{
ID: arg.ID,
CreatedAt: secret.CreatedAt,
SecretPrefix: secret.SecretPrefix,
HashedSecret: secret.HashedSecret,
DisplaySecret: secret.DisplaySecret,
AppID: secret.AppID,
+70
View File
@@ -225,6 +225,20 @@ func (m metricsStore) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.U
return r0
}
func (m metricsStore) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteOAuth2ProviderAppCodeByID(ctx, id)
m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppCodeByID").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
start := time.Now()
r0 := m.s.DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx, arg)
m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppCodesByAppAndUserID").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteOAuth2ProviderAppSecretByID(ctx, id)
@@ -232,6 +246,13 @@ func (m metricsStore) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id
return r0
}
func (m metricsStore) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error {
start := time.Now()
r0 := m.s.DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx, arg)
m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppTokensByAppAndUserID").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) DeleteOldProvisionerDaemons(ctx context.Context) error {
start := time.Now()
r0 := m.s.DeleteOldProvisionerDaemons(ctx)
@@ -615,6 +636,20 @@ func (m metricsStore) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppCodeByID(ctx, id)
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppCodeByID").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (database.OAuth2ProviderAppCode, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppCodeByPrefix(ctx, secretPrefix)
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppCodeByPrefix").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppSecretByID(ctx, id)
@@ -622,6 +657,13 @@ func (m metricsStore) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uui
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (database.OAuth2ProviderAppSecret, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppSecretByPrefix(ctx, secretPrefix)
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppSecretByPrefix").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]database.OAuth2ProviderAppSecret, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppSecretsByAppID(ctx, appID)
@@ -629,6 +671,13 @@ func (m metricsStore) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, ap
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (database.OAuth2ProviderAppToken, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppTokenByPrefix(ctx, hashPrefix)
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppTokenByPrefix").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAuth2ProviderApp, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderApps(ctx)
@@ -636,6 +685,13 @@ func (m metricsStore) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAu
return r0, r1
}
func (m metricsStore) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) {
start := time.Now()
r0, r1 := m.s.GetOAuth2ProviderAppsByUserID(ctx, userID)
m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppsByUserID").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetOAuthSigningKey(ctx context.Context) (string, error) {
start := time.Now()
r0, r1 := m.s.GetOAuthSigningKey(ctx)
@@ -1425,6 +1481,13 @@ func (m metricsStore) InsertOAuth2ProviderApp(ctx context.Context, arg database.
return r0, r1
}
func (m metricsStore) InsertOAuth2ProviderAppCode(ctx context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) {
start := time.Now()
r0, r1 := m.s.InsertOAuth2ProviderAppCode(ctx, arg)
m.queryLatencies.WithLabelValues("InsertOAuth2ProviderAppCode").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) InsertOAuth2ProviderAppSecret(ctx context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) {
start := time.Now()
r0, r1 := m.s.InsertOAuth2ProviderAppSecret(ctx, arg)
@@ -1432,6 +1495,13 @@ func (m metricsStore) InsertOAuth2ProviderAppSecret(ctx context.Context, arg dat
return r0, r1
}
func (m metricsStore) InsertOAuth2ProviderAppToken(ctx context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) {
start := time.Now()
r0, r1 := m.s.InsertOAuth2ProviderAppToken(ctx, arg)
m.queryLatencies.WithLabelValues("InsertOAuth2ProviderAppToken").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) InsertOrganization(ctx context.Context, arg database.InsertOrganizationParams) (database.Organization, error) {
start := time.Now()
organization, err := m.s.InsertOrganization(ctx, arg)
+147
View File
@@ -342,6 +342,34 @@ func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppByID(arg0, arg1 any) *go
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppByID), arg0, arg1)
}
// DeleteOAuth2ProviderAppCodeByID mocks base method.
func (m *MockStore) DeleteOAuth2ProviderAppCodeByID(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppCodeByID", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteOAuth2ProviderAppCodeByID indicates an expected call of DeleteOAuth2ProviderAppCodeByID.
func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppCodeByID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppCodeByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppCodeByID), arg0, arg1)
}
// DeleteOAuth2ProviderAppCodesByAppAndUserID mocks base method.
func (m *MockStore) DeleteOAuth2ProviderAppCodesByAppAndUserID(arg0 context.Context, arg1 database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppCodesByAppAndUserID", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteOAuth2ProviderAppCodesByAppAndUserID indicates an expected call of DeleteOAuth2ProviderAppCodesByAppAndUserID.
func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppCodesByAppAndUserID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppCodesByAppAndUserID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppCodesByAppAndUserID), arg0, arg1)
}
// DeleteOAuth2ProviderAppSecretByID mocks base method.
func (m *MockStore) DeleteOAuth2ProviderAppSecretByID(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()
@@ -356,6 +384,20 @@ func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppSecretByID(arg0, arg1 an
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppSecretByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppSecretByID), arg0, arg1)
}
// DeleteOAuth2ProviderAppTokensByAppAndUserID mocks base method.
func (m *MockStore) DeleteOAuth2ProviderAppTokensByAppAndUserID(arg0 context.Context, arg1 database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppTokensByAppAndUserID", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteOAuth2ProviderAppTokensByAppAndUserID indicates an expected call of DeleteOAuth2ProviderAppTokensByAppAndUserID.
func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppTokensByAppAndUserID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppTokensByAppAndUserID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppTokensByAppAndUserID), arg0, arg1)
}
// DeleteOldProvisionerDaemons mocks base method.
func (m *MockStore) DeleteOldProvisionerDaemons(arg0 context.Context) error {
m.ctrl.T.Helper()
@@ -1219,6 +1261,36 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppByID(arg0, arg1 any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppByID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppByID), arg0, arg1)
}
// GetOAuth2ProviderAppCodeByID mocks base method.
func (m *MockStore) GetOAuth2ProviderAppCodeByID(arg0 context.Context, arg1 uuid.UUID) (database.OAuth2ProviderAppCode, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppCodeByID", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppCode)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetOAuth2ProviderAppCodeByID indicates an expected call of GetOAuth2ProviderAppCodeByID.
func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppCodeByID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppCodeByID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppCodeByID), arg0, arg1)
}
// GetOAuth2ProviderAppCodeByPrefix mocks base method.
func (m *MockStore) GetOAuth2ProviderAppCodeByPrefix(arg0 context.Context, arg1 []byte) (database.OAuth2ProviderAppCode, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppCodeByPrefix", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppCode)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetOAuth2ProviderAppCodeByPrefix indicates an expected call of GetOAuth2ProviderAppCodeByPrefix.
func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppCodeByPrefix(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppCodeByPrefix", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppCodeByPrefix), arg0, arg1)
}
// GetOAuth2ProviderAppSecretByID mocks base method.
func (m *MockStore) GetOAuth2ProviderAppSecretByID(arg0 context.Context, arg1 uuid.UUID) (database.OAuth2ProviderAppSecret, error) {
m.ctrl.T.Helper()
@@ -1234,6 +1306,21 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppSecretByID(arg0, arg1 any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppSecretByID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppSecretByID), arg0, arg1)
}
// GetOAuth2ProviderAppSecretByPrefix mocks base method.
func (m *MockStore) GetOAuth2ProviderAppSecretByPrefix(arg0 context.Context, arg1 []byte) (database.OAuth2ProviderAppSecret, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppSecretByPrefix", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppSecret)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetOAuth2ProviderAppSecretByPrefix indicates an expected call of GetOAuth2ProviderAppSecretByPrefix.
func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppSecretByPrefix(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppSecretByPrefix", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppSecretByPrefix), arg0, arg1)
}
// GetOAuth2ProviderAppSecretsByAppID mocks base method.
func (m *MockStore) GetOAuth2ProviderAppSecretsByAppID(arg0 context.Context, arg1 uuid.UUID) ([]database.OAuth2ProviderAppSecret, error) {
m.ctrl.T.Helper()
@@ -1249,6 +1336,21 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppSecretsByAppID(arg0, arg1 a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppSecretsByAppID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppSecretsByAppID), arg0, arg1)
}
// GetOAuth2ProviderAppTokenByPrefix mocks base method.
func (m *MockStore) GetOAuth2ProviderAppTokenByPrefix(arg0 context.Context, arg1 []byte) (database.OAuth2ProviderAppToken, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppTokenByPrefix", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppToken)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetOAuth2ProviderAppTokenByPrefix indicates an expected call of GetOAuth2ProviderAppTokenByPrefix.
func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppTokenByPrefix(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppTokenByPrefix", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppTokenByPrefix), arg0, arg1)
}
// GetOAuth2ProviderApps mocks base method.
func (m *MockStore) GetOAuth2ProviderApps(arg0 context.Context) ([]database.OAuth2ProviderApp, error) {
m.ctrl.T.Helper()
@@ -1264,6 +1366,21 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderApps(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderApps", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderApps), arg0)
}
// GetOAuth2ProviderAppsByUserID mocks base method.
func (m *MockStore) GetOAuth2ProviderAppsByUserID(arg0 context.Context, arg1 uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetOAuth2ProviderAppsByUserID", arg0, arg1)
ret0, _ := ret[0].([]database.GetOAuth2ProviderAppsByUserIDRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetOAuth2ProviderAppsByUserID indicates an expected call of GetOAuth2ProviderAppsByUserID.
func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppsByUserID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppsByUserID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppsByUserID), arg0, arg1)
}
// GetOAuthSigningKey mocks base method.
func (m *MockStore) GetOAuthSigningKey(arg0 context.Context) (string, error) {
m.ctrl.T.Helper()
@@ -2999,6 +3116,21 @@ func (mr *MockStoreMockRecorder) InsertOAuth2ProviderApp(arg0, arg1 any) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderApp", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderApp), arg0, arg1)
}
// InsertOAuth2ProviderAppCode mocks base method.
func (m *MockStore) InsertOAuth2ProviderAppCode(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertOAuth2ProviderAppCode", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppCode)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertOAuth2ProviderAppCode indicates an expected call of InsertOAuth2ProviderAppCode.
func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppCode(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppCode", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppCode), arg0, arg1)
}
// InsertOAuth2ProviderAppSecret mocks base method.
func (m *MockStore) InsertOAuth2ProviderAppSecret(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) {
m.ctrl.T.Helper()
@@ -3014,6 +3146,21 @@ func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppSecret(arg0, arg1 any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppSecret", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppSecret), arg0, arg1)
}
// InsertOAuth2ProviderAppToken mocks base method.
func (m *MockStore) InsertOAuth2ProviderAppToken(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertOAuth2ProviderAppToken", arg0, arg1)
ret0, _ := ret[0].(database.OAuth2ProviderAppToken)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertOAuth2ProviderAppToken indicates an expected call of InsertOAuth2ProviderAppToken.
func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppToken(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppToken", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppToken), arg0, arg1)
}
// InsertOrganization mocks base method.
func (m *MockStore) InsertOrganization(arg0 context.Context, arg1 database.InsertOrganizationParams) (database.Organization, error) {
m.ctrl.T.Helper()
+67 -4
View File
@@ -67,7 +67,8 @@ CREATE TYPE login_type AS ENUM (
'github',
'oidc',
'token',
'none'
'none',
'oauth2_provider_app'
);
COMMENT ON TYPE login_type IS 'Specifies the method of authentication. "none" is a special case in which no authentication method is allowed.';
@@ -187,6 +188,17 @@ CREATE TYPE workspace_transition AS ENUM (
'delete'
);
CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
DELETE FROM api_keys
WHERE id = OLD.api_key_id;
RETURN OLD;
END;
$$;
CREATE FUNCTION delete_deleted_user_resources() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -490,17 +502,42 @@ CREATE SEQUENCE licenses_id_seq
ALTER SEQUENCE licenses_id_seq OWNED BY licenses.id;
CREATE TABLE oauth2_provider_app_codes (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
secret_prefix bytea NOT NULL,
hashed_secret bytea NOT NULL,
user_id uuid NOT NULL,
app_id uuid NOT NULL
);
COMMENT ON TABLE oauth2_provider_app_codes IS 'Codes are meant to be exchanged for access tokens.';
CREATE TABLE oauth2_provider_app_secrets (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
last_used_at timestamp with time zone,
hashed_secret bytea NOT NULL,
display_secret text NOT NULL,
app_id uuid NOT NULL
app_id uuid NOT NULL,
secret_prefix bytea NOT NULL
);
COMMENT ON COLUMN oauth2_provider_app_secrets.display_secret IS 'The tail end of the original secret so secrets can be differentiated.';
CREATE TABLE oauth2_provider_app_tokens (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
hash_prefix bytea NOT NULL,
refresh_hash bytea NOT NULL,
app_secret_id uuid NOT NULL,
api_key_id text NOT NULL
);
COMMENT ON COLUMN oauth2_provider_app_tokens.refresh_hash IS 'Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.';
CREATE TABLE oauth2_provider_apps (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -1354,12 +1391,24 @@ ALTER TABLE ONLY licenses
ALTER TABLE ONLY licenses
ADD CONSTRAINT licenses_pkey PRIMARY KEY (id);
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret);
ALTER TABLE ONLY oauth2_provider_app_codes
ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id);
ALTER TABLE ONLY oauth2_provider_app_codes
ADD CONSTRAINT oauth2_provider_app_codes_secret_prefix_key UNIQUE (secret_prefix);
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id);
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key UNIQUE (secret_prefix);
ALTER TABLE ONLY oauth2_provider_app_tokens
ADD CONSTRAINT oauth2_provider_app_tokens_hash_prefix_key UNIQUE (hash_prefix);
ALTER TABLE ONLY oauth2_provider_app_tokens
ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id);
ALTER TABLE ONLY oauth2_provider_apps
ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name);
@@ -1572,6 +1621,8 @@ CREATE TRIGGER tailnet_notify_peer_change AFTER INSERT OR DELETE OR UPDATE ON ta
CREATE TRIGGER tailnet_notify_tunnel_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_tunnels FOR EACH ROW EXECUTE FUNCTION tailnet_notify_tunnel_change();
CREATE TRIGGER trigger_delete_oauth2_provider_app_token AFTER DELETE ON oauth2_provider_app_tokens FOR EACH ROW EXECUTE FUNCTION delete_deleted_oauth2_provider_app_token_api_key();
CREATE TRIGGER trigger_insert_apikeys BEFORE INSERT ON api_keys FOR EACH ROW EXECUTE FUNCTION insert_apikey_fail_if_user_deleted();
CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_resources();
@@ -1605,9 +1656,21 @@ ALTER TABLE ONLY jfrog_xray_scans
ALTER TABLE ONLY jfrog_xray_scans
ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth2_provider_app_codes
ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth2_provider_app_codes
ADD CONSTRAINT oauth2_provider_app_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth2_provider_app_tokens
ADD CONSTRAINT oauth2_provider_app_tokens_api_key_id_fkey FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE;
ALTER TABLE ONLY oauth2_provider_app_tokens
ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_fkey FOREIGN KEY (app_secret_id) REFERENCES oauth2_provider_app_secrets(id) ON DELETE CASCADE;
ALTER TABLE ONLY organization_members
ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
@@ -15,7 +15,11 @@ const (
ForeignKeyGroupsOrganizationID ForeignKeyConstraint = "groups_organization_id_fkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyJfrogXrayScansAgentID ForeignKeyConstraint = "jfrog_xray_scans_agent_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
ForeignKeyJfrogXrayScansWorkspaceID ForeignKeyConstraint = "jfrog_xray_scans_workspace_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
ForeignKeyOauth2ProviderAppCodesAppID ForeignKeyConstraint = "oauth2_provider_app_codes_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
ForeignKeyOauth2ProviderAppCodesUserID ForeignKeyConstraint = "oauth2_provider_app_codes_user_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ForeignKeyOauth2ProviderAppSecretsAppID ForeignKeyConstraint = "oauth2_provider_app_secrets_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE;
ForeignKeyOauth2ProviderAppTokensAPIKeyID ForeignKeyConstraint = "oauth2_provider_app_tokens_api_key_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_api_key_id_fkey FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE;
ForeignKeyOauth2ProviderAppTokensAppSecretID ForeignKeyConstraint = "oauth2_provider_app_tokens_app_secret_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_fkey FOREIGN KEY (app_secret_id) REFERENCES oauth2_provider_app_secrets(id) ON DELETE CASCADE;
ForeignKeyOrganizationMembersOrganizationIDUUID ForeignKeyConstraint = "organization_members_organization_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyOrganizationMembersUserIDUUID ForeignKeyConstraint = "organization_members_user_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
@@ -0,0 +1,18 @@
DROP TRIGGER IF EXISTS trigger_delete_oauth2_provider_app_token ON oauth2_provider_app_tokens;
DROP FUNCTION IF EXISTS delete_deleted_oauth2_provider_app_token_api_key;
DROP TABLE oauth2_provider_app_tokens;
DROP TABLE oauth2_provider_app_codes;
-- It is not possible to drop enum values from enum types, so the UP on
-- login_type has "IF NOT EXISTS".
-- The constraints on the secret prefix (which is used as an id embedded in the
-- secret) are dropped, but avoid completely reverting back to the previous
-- behavior since that will render existing secrets unusable once upgraded
-- again. OAuth2 is blocked outside of development mode in previous versions,
-- so users will not be able to create broken secrets. This is really just to
-- make sure tests keep working (say for a bisect).
ALTER TABLE ONLY oauth2_provider_app_secrets
DROP CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key,
ALTER COLUMN secret_prefix DROP NOT NULL;
@@ -0,0 +1,65 @@
CREATE TABLE oauth2_provider_app_codes (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
secret_prefix bytea NOT NULL,
hashed_secret bytea NOT NULL,
user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE,
app_id uuid NOT NULL REFERENCES oauth2_provider_apps (id) ON DELETE CASCADE,
PRIMARY KEY (id),
UNIQUE(secret_prefix)
);
COMMENT ON TABLE oauth2_provider_app_codes IS 'Codes are meant to be exchanged for access tokens.';
CREATE TABLE oauth2_provider_app_tokens (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
hash_prefix bytea NOT NULL,
refresh_hash bytea NOT NULL,
app_secret_id uuid NOT NULL REFERENCES oauth2_provider_app_secrets (id) ON DELETE CASCADE,
api_key_id text NOT NULL REFERENCES api_keys (id) ON DELETE CASCADE,
PRIMARY KEY (id),
UNIQUE(hash_prefix)
);
COMMENT ON COLUMN oauth2_provider_app_tokens.refresh_hash IS 'Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.';
-- When we delete a token, delete the API key associated with it.
CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
BEGIN
DELETE FROM api_keys
WHERE id = OLD.api_key_id;
RETURN OLD;
END;
$$;
CREATE TRIGGER trigger_delete_oauth2_provider_app_token
AFTER DELETE ON oauth2_provider_app_tokens
FOR EACH ROW
EXECUTE PROCEDURE delete_deleted_oauth2_provider_app_token_api_key();
ALTER TYPE login_type ADD VALUE IF NOT EXISTS 'oauth2_provider_app';
-- Switch to an ID we will prefix to the raw secret that we give to the user
-- (instead of matching on the entire secret as the ID, since they will be
-- salted and we can no longer do that). OAuth2 is blocked outside of
-- development mode so there should be no production secrets unless they
-- previously upgraded, in which case they keep their original prefixes and will
-- be fine. Add a random ID for the development mode case so the upgrade does
-- not fail, at least.
ALTER TABLE ONLY oauth2_provider_app_secrets
ADD COLUMN IF NOT EXISTS secret_prefix bytea NULL;
UPDATE oauth2_provider_app_secrets
SET secret_prefix = substr(md5(random()::text), 0, 10)::bytea
WHERE secret_prefix IS NULL;
ALTER TABLE ONLY oauth2_provider_app_secrets
ALTER COLUMN secret_prefix SET NOT NULL,
ADD CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key UNIQUE (secret_prefix),
DROP CONSTRAINT IF EXISTS oauth2_provider_app_secrets_app_id_hashed_secret_key;
@@ -0,0 +1,23 @@
INSERT INTO oauth2_provider_app_codes
(id, created_at, expires_at, secret_prefix, hashed_secret, user_id, app_id)
VALUES (
'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'2023-06-15 10:23:54+00',
'2023-06-15 10:23:54+00',
CAST('abcdefg' AS bytea),
CAST('abcdefg' AS bytea),
'0ed9befc-4911-4ccf-a8e2-559bf72daa94',
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
);
INSERT INTO oauth2_provider_app_tokens
(id, created_at, expires_at, hash_prefix, refresh_hash, app_secret_id, api_key_id)
VALUES (
'd0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'2023-06-15 10:25:33+00',
'2023-12-15 11:40:20+00',
CAST('gfedcba' AS bytea),
CAST('abcdefg' AS bytea),
'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
'peuLZhMXt4'
);
+16
View File
@@ -290,6 +290,22 @@ func (l License) RBACObject() rbac.Object {
return rbac.ResourceLicense.WithIDString(strconv.FormatInt(int64(l.ID), 10))
}
func (c OAuth2ProviderAppCode) RBACObject() rbac.Object {
return rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(c.UserID.String())
}
func (OAuth2ProviderAppSecret) RBACObject() rbac.Object {
return rbac.ResourceOAuth2ProviderAppSecret
}
func (OAuth2ProviderApp) RBACObject() rbac.Object {
return rbac.ResourceOAuth2ProviderApp
}
func (a GetOAuth2ProviderAppsByUserIDRow) RBACObject() rbac.Object {
return a.OAuth2ProviderApp.RBACObject()
}
type WorkspaceAgentConnectionStatus struct {
Status WorkspaceAgentStatus `json:"status"`
FirstConnectedAt *time.Time `json:"first_connected_at"`
+32 -6
View File
@@ -593,11 +593,12 @@ func AllLogSourceValues() []LogSource {
type LoginType string
const (
LoginTypePassword LoginType = "password"
LoginTypeGithub LoginType = "github"
LoginTypeOIDC LoginType = "oidc"
LoginTypeToken LoginType = "token"
LoginTypeNone LoginType = "none"
LoginTypePassword LoginType = "password"
LoginTypeGithub LoginType = "github"
LoginTypeOIDC LoginType = "oidc"
LoginTypeToken LoginType = "token"
LoginTypeNone LoginType = "none"
LoginTypeOAuth2ProviderApp LoginType = "oauth2_provider_app"
)
func (e *LoginType) Scan(src interface{}) error {
@@ -641,7 +642,8 @@ func (e LoginType) Valid() bool {
LoginTypeGithub,
LoginTypeOIDC,
LoginTypeToken,
LoginTypeNone:
LoginTypeNone,
LoginTypeOAuth2ProviderApp:
return true
}
return false
@@ -654,6 +656,7 @@ func AllLoginTypeValues() []LoginType {
LoginTypeOIDC,
LoginTypeToken,
LoginTypeNone,
LoginTypeOAuth2ProviderApp,
}
}
@@ -1807,6 +1810,17 @@ type OAuth2ProviderApp struct {
CallbackURL string `db:"callback_url" json:"callback_url"`
}
// Codes are meant to be exchanged for access tokens.
type OAuth2ProviderAppCode struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
SecretPrefix []byte `db:"secret_prefix" json:"secret_prefix"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
AppID uuid.UUID `db:"app_id" json:"app_id"`
}
type OAuth2ProviderAppSecret struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
@@ -1815,6 +1829,18 @@ type OAuth2ProviderAppSecret struct {
// The tail end of the original secret so secrets can be differentiated.
DisplaySecret string `db:"display_secret" json:"display_secret"`
AppID uuid.UUID `db:"app_id" json:"app_id"`
SecretPrefix []byte `db:"secret_prefix" json:"secret_prefix"`
}
type OAuth2ProviderAppToken struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
HashPrefix []byte `db:"hash_prefix" json:"hash_prefix"`
// Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.
RefreshHash []byte `db:"refresh_hash" json:"refresh_hash"`
AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"`
APIKeyID string `db:"api_key_id" json:"api_key_id"`
}
type Organization struct {
+10
View File
@@ -60,7 +60,10 @@ type sqlcQuerier interface {
DeleteGroupMemberFromGroup(ctx context.Context, arg DeleteGroupMemberFromGroupParams) error
DeleteLicense(ctx context.Context, id int32) (int32, error)
DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) error
DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error
DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error
DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error
DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error
// Delete provisioner daemons that have been created at least a week ago
// and have not connected to coderd since a week.
// A provisioner daemon with "zeroed" last_seen_at column indicates possible
@@ -131,9 +134,14 @@ type sqlcQuerier interface {
GetLicenses(ctx context.Context) ([]License, error)
GetLogoURL(ctx context.Context) (string, error)
GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error)
GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error)
GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error)
GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error)
GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppSecret, error)
GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error)
GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (OAuth2ProviderAppToken, error)
GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2ProviderApp, error)
GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error)
GetOAuthSigningKey(ctx context.Context) (string, error)
GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error)
GetOrganizationByName(ctx context.Context, name string) (Organization, error)
@@ -290,7 +298,9 @@ type sqlcQuerier interface {
// If the name conflicts, do nothing.
InsertMissingGroups(ctx context.Context, arg InsertMissingGroupsParams) ([]Group, error)
InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAuth2ProviderAppParams) (OAuth2ProviderApp, error)
InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error)
InsertOAuth2ProviderAppSecret(ctx context.Context, arg InsertOAuth2ProviderAppSecretParams) (OAuth2ProviderAppSecret, error)
InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error)
InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error)
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
+293 -5
View File
@@ -2691,6 +2691,29 @@ func (q *sqlQuerier) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UU
return err
}
const deleteOAuth2ProviderAppCodeByID = `-- name: DeleteOAuth2ProviderAppCodeByID :exec
DELETE FROM oauth2_provider_app_codes WHERE id = $1
`
func (q *sqlQuerier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppCodeByID, id)
return err
}
const deleteOAuth2ProviderAppCodesByAppAndUserID = `-- name: DeleteOAuth2ProviderAppCodesByAppAndUserID :exec
DELETE FROM oauth2_provider_app_codes WHERE app_id = $1 AND user_id = $2
`
type DeleteOAuth2ProviderAppCodesByAppAndUserIDParams struct {
AppID uuid.UUID `db:"app_id" json:"app_id"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error {
_, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppCodesByAppAndUserID, arg.AppID, arg.UserID)
return err
}
const deleteOAuth2ProviderAppSecretByID = `-- name: DeleteOAuth2ProviderAppSecretByID :exec
DELETE FROM oauth2_provider_app_secrets WHERE id = $1
`
@@ -2700,6 +2723,28 @@ func (q *sqlQuerier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id u
return err
}
const deleteOAuth2ProviderAppTokensByAppAndUserID = `-- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec
DELETE FROM
oauth2_provider_app_tokens
USING
oauth2_provider_app_secrets, api_keys
WHERE
oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id
AND api_keys.id = oauth2_provider_app_tokens.api_key_id
AND oauth2_provider_app_secrets.app_id = $1
AND api_keys.user_id = $2
`
type DeleteOAuth2ProviderAppTokensByAppAndUserIDParams struct {
AppID uuid.UUID `db:"app_id" json:"app_id"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error {
_, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppTokensByAppAndUserID, arg.AppID, arg.UserID)
return err
}
const getOAuth2ProviderAppByID = `-- name: GetOAuth2ProviderAppByID :one
SELECT id, created_at, updated_at, name, icon, callback_url FROM oauth2_provider_apps WHERE id = $1
`
@@ -2718,8 +2763,46 @@ func (q *sqlQuerier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID)
return i, err
}
const getOAuth2ProviderAppCodeByID = `-- name: GetOAuth2ProviderAppCodeByID :one
SELECT id, created_at, expires_at, secret_prefix, hashed_secret, user_id, app_id FROM oauth2_provider_app_codes WHERE id = $1
`
func (q *sqlQuerier) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) {
row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppCodeByID, id)
var i OAuth2ProviderAppCode
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.ExpiresAt,
&i.SecretPrefix,
&i.HashedSecret,
&i.UserID,
&i.AppID,
)
return i, err
}
const getOAuth2ProviderAppCodeByPrefix = `-- name: GetOAuth2ProviderAppCodeByPrefix :one
SELECT id, created_at, expires_at, secret_prefix, hashed_secret, user_id, app_id FROM oauth2_provider_app_codes WHERE secret_prefix = $1
`
func (q *sqlQuerier) GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error) {
row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppCodeByPrefix, secretPrefix)
var i OAuth2ProviderAppCode
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.ExpiresAt,
&i.SecretPrefix,
&i.HashedSecret,
&i.UserID,
&i.AppID,
)
return i, err
}
const getOAuth2ProviderAppSecretByID = `-- name: GetOAuth2ProviderAppSecretByID :one
SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id FROM oauth2_provider_app_secrets WHERE id = $1
SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id, secret_prefix FROM oauth2_provider_app_secrets WHERE id = $1
`
func (q *sqlQuerier) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error) {
@@ -2732,12 +2815,32 @@ func (q *sqlQuerier) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid
&i.HashedSecret,
&i.DisplaySecret,
&i.AppID,
&i.SecretPrefix,
)
return i, err
}
const getOAuth2ProviderAppSecretByPrefix = `-- name: GetOAuth2ProviderAppSecretByPrefix :one
SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id, secret_prefix FROM oauth2_provider_app_secrets WHERE secret_prefix = $1
`
func (q *sqlQuerier) GetOAuth2ProviderAppSecretByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppSecret, error) {
row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppSecretByPrefix, secretPrefix)
var i OAuth2ProviderAppSecret
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.LastUsedAt,
&i.HashedSecret,
&i.DisplaySecret,
&i.AppID,
&i.SecretPrefix,
)
return i, err
}
const getOAuth2ProviderAppSecretsByAppID = `-- name: GetOAuth2ProviderAppSecretsByAppID :many
SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id FROM oauth2_provider_app_secrets WHERE app_id = $1 ORDER BY (created_at, id) ASC
SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id, secret_prefix FROM oauth2_provider_app_secrets WHERE app_id = $1 ORDER BY (created_at, id) ASC
`
func (q *sqlQuerier) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error) {
@@ -2756,6 +2859,7 @@ func (q *sqlQuerier) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, app
&i.HashedSecret,
&i.DisplaySecret,
&i.AppID,
&i.SecretPrefix,
); err != nil {
return nil, err
}
@@ -2770,6 +2874,25 @@ func (q *sqlQuerier) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, app
return items, nil
}
const getOAuth2ProviderAppTokenByPrefix = `-- name: GetOAuth2ProviderAppTokenByPrefix :one
SELECT id, created_at, expires_at, hash_prefix, refresh_hash, app_secret_id, api_key_id FROM oauth2_provider_app_tokens WHERE hash_prefix = $1
`
func (q *sqlQuerier) GetOAuth2ProviderAppTokenByPrefix(ctx context.Context, hashPrefix []byte) (OAuth2ProviderAppToken, error) {
row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppTokenByPrefix, hashPrefix)
var i OAuth2ProviderAppToken
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.ExpiresAt,
&i.HashPrefix,
&i.RefreshHash,
&i.AppSecretID,
&i.APIKeyID,
)
return i, err
}
const getOAuth2ProviderApps = `-- name: GetOAuth2ProviderApps :many
SELECT id, created_at, updated_at, name, icon, callback_url FROM oauth2_provider_apps ORDER BY (name, id) ASC
`
@@ -2804,6 +2927,59 @@ func (q *sqlQuerier) GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2Provide
return items, nil
}
const getOAuth2ProviderAppsByUserID = `-- name: GetOAuth2ProviderAppsByUserID :many
SELECT
COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count,
oauth2_provider_apps.id, oauth2_provider_apps.created_at, oauth2_provider_apps.updated_at, oauth2_provider_apps.name, oauth2_provider_apps.icon, oauth2_provider_apps.callback_url
FROM oauth2_provider_app_tokens
INNER JOIN oauth2_provider_app_secrets
ON oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id
INNER JOIN oauth2_provider_apps
ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id
INNER JOIN api_keys
ON api_keys.id = oauth2_provider_app_tokens.api_key_id
WHERE
api_keys.user_id = $1
GROUP BY
oauth2_provider_apps.id
`
type GetOAuth2ProviderAppsByUserIDRow struct {
TokenCount int64 `db:"token_count" json:"token_count"`
OAuth2ProviderApp OAuth2ProviderApp `db:"oauth2_provider_app" json:"oauth2_provider_app"`
}
func (q *sqlQuerier) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error) {
rows, err := q.db.QueryContext(ctx, getOAuth2ProviderAppsByUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetOAuth2ProviderAppsByUserIDRow
for rows.Next() {
var i GetOAuth2ProviderAppsByUserIDRow
if err := rows.Scan(
&i.TokenCount,
&i.OAuth2ProviderApp.ID,
&i.OAuth2ProviderApp.CreatedAt,
&i.OAuth2ProviderApp.UpdatedAt,
&i.OAuth2ProviderApp.Name,
&i.OAuth2ProviderApp.Icon,
&i.OAuth2ProviderApp.CallbackURL,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertOAuth2ProviderApp = `-- name: InsertOAuth2ProviderApp :one
INSERT INTO oauth2_provider_apps (
id,
@@ -2852,10 +3028,64 @@ func (q *sqlQuerier) InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAut
return i, err
}
const insertOAuth2ProviderAppCode = `-- name: InsertOAuth2ProviderAppCode :one
INSERT INTO oauth2_provider_app_codes (
id,
created_at,
expires_at,
secret_prefix,
hashed_secret,
app_id,
user_id
) VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
) RETURNING id, created_at, expires_at, secret_prefix, hashed_secret, user_id, app_id
`
type InsertOAuth2ProviderAppCodeParams struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
SecretPrefix []byte `db:"secret_prefix" json:"secret_prefix"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
AppID uuid.UUID `db:"app_id" json:"app_id"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error) {
row := q.db.QueryRowContext(ctx, insertOAuth2ProviderAppCode,
arg.ID,
arg.CreatedAt,
arg.ExpiresAt,
arg.SecretPrefix,
arg.HashedSecret,
arg.AppID,
arg.UserID,
)
var i OAuth2ProviderAppCode
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.ExpiresAt,
&i.SecretPrefix,
&i.HashedSecret,
&i.UserID,
&i.AppID,
)
return i, err
}
const insertOAuth2ProviderAppSecret = `-- name: InsertOAuth2ProviderAppSecret :one
INSERT INTO oauth2_provider_app_secrets (
id,
created_at,
secret_prefix,
hashed_secret,
display_secret,
app_id
@@ -2864,13 +3094,15 @@ INSERT INTO oauth2_provider_app_secrets (
$2,
$3,
$4,
$5
) RETURNING id, created_at, last_used_at, hashed_secret, display_secret, app_id
$5,
$6
) RETURNING id, created_at, last_used_at, hashed_secret, display_secret, app_id, secret_prefix
`
type InsertOAuth2ProviderAppSecretParams struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
SecretPrefix []byte `db:"secret_prefix" json:"secret_prefix"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
DisplaySecret string `db:"display_secret" json:"display_secret"`
AppID uuid.UUID `db:"app_id" json:"app_id"`
@@ -2880,6 +3112,7 @@ func (q *sqlQuerier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg Inse
row := q.db.QueryRowContext(ctx, insertOAuth2ProviderAppSecret,
arg.ID,
arg.CreatedAt,
arg.SecretPrefix,
arg.HashedSecret,
arg.DisplaySecret,
arg.AppID,
@@ -2892,6 +3125,60 @@ func (q *sqlQuerier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg Inse
&i.HashedSecret,
&i.DisplaySecret,
&i.AppID,
&i.SecretPrefix,
)
return i, err
}
const insertOAuth2ProviderAppToken = `-- name: InsertOAuth2ProviderAppToken :one
INSERT INTO oauth2_provider_app_tokens (
id,
created_at,
expires_at,
hash_prefix,
refresh_hash,
app_secret_id,
api_key_id
) VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
) RETURNING id, created_at, expires_at, hash_prefix, refresh_hash, app_secret_id, api_key_id
`
type InsertOAuth2ProviderAppTokenParams struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
HashPrefix []byte `db:"hash_prefix" json:"hash_prefix"`
RefreshHash []byte `db:"refresh_hash" json:"refresh_hash"`
AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"`
APIKeyID string `db:"api_key_id" json:"api_key_id"`
}
func (q *sqlQuerier) InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) {
row := q.db.QueryRowContext(ctx, insertOAuth2ProviderAppToken,
arg.ID,
arg.CreatedAt,
arg.ExpiresAt,
arg.HashPrefix,
arg.RefreshHash,
arg.AppSecretID,
arg.APIKeyID,
)
var i OAuth2ProviderAppToken
err := row.Scan(
&i.ID,
&i.CreatedAt,
&i.ExpiresAt,
&i.HashPrefix,
&i.RefreshHash,
&i.AppSecretID,
&i.APIKeyID,
)
return i, err
}
@@ -2936,7 +3223,7 @@ func (q *sqlQuerier) UpdateOAuth2ProviderAppByID(ctx context.Context, arg Update
const updateOAuth2ProviderAppSecretByID = `-- name: UpdateOAuth2ProviderAppSecretByID :one
UPDATE oauth2_provider_app_secrets SET
last_used_at = $2
WHERE id = $1 RETURNING id, created_at, last_used_at, hashed_secret, display_secret, app_id
WHERE id = $1 RETURNING id, created_at, last_used_at, hashed_secret, display_secret, app_id, secret_prefix
`
type UpdateOAuth2ProviderAppSecretByIDParams struct {
@@ -2954,6 +3241,7 @@ func (q *sqlQuerier) UpdateOAuth2ProviderAppSecretByID(ctx context.Context, arg
&i.HashedSecret,
&i.DisplaySecret,
&i.AppID,
&i.SecretPrefix,
)
return i, err
}
+86 -1
View File
@@ -38,10 +38,14 @@ SELECT * FROM oauth2_provider_app_secrets WHERE id = $1;
-- name: GetOAuth2ProviderAppSecretsByAppID :many
SELECT * FROM oauth2_provider_app_secrets WHERE app_id = $1 ORDER BY (created_at, id) ASC;
-- name: GetOAuth2ProviderAppSecretByPrefix :one
SELECT * FROM oauth2_provider_app_secrets WHERE secret_prefix = $1;
-- name: InsertOAuth2ProviderAppSecret :one
INSERT INTO oauth2_provider_app_secrets (
id,
created_at,
secret_prefix,
hashed_secret,
display_secret,
app_id
@@ -50,7 +54,8 @@ INSERT INTO oauth2_provider_app_secrets (
$2,
$3,
$4,
$5
$5,
$6
) RETURNING *;
-- name: UpdateOAuth2ProviderAppSecretByID :one
@@ -60,3 +65,83 @@ WHERE id = $1 RETURNING *;
-- name: DeleteOAuth2ProviderAppSecretByID :exec
DELETE FROM oauth2_provider_app_secrets WHERE id = $1;
-- name: GetOAuth2ProviderAppCodeByID :one
SELECT * FROM oauth2_provider_app_codes WHERE id = $1;
-- name: GetOAuth2ProviderAppCodeByPrefix :one
SELECT * FROM oauth2_provider_app_codes WHERE secret_prefix = $1;
-- name: InsertOAuth2ProviderAppCode :one
INSERT INTO oauth2_provider_app_codes (
id,
created_at,
expires_at,
secret_prefix,
hashed_secret,
app_id,
user_id
) VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
) RETURNING *;
-- name: DeleteOAuth2ProviderAppCodeByID :exec
DELETE FROM oauth2_provider_app_codes WHERE id = $1;
-- name: DeleteOAuth2ProviderAppCodesByAppAndUserID :exec
DELETE FROM oauth2_provider_app_codes WHERE app_id = $1 AND user_id = $2;
-- name: InsertOAuth2ProviderAppToken :one
INSERT INTO oauth2_provider_app_tokens (
id,
created_at,
expires_at,
hash_prefix,
refresh_hash,
app_secret_id,
api_key_id
) VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
) RETURNING *;
-- name: GetOAuth2ProviderAppTokenByPrefix :one
SELECT * FROM oauth2_provider_app_tokens WHERE hash_prefix = $1;
-- name: GetOAuth2ProviderAppsByUserID :many
SELECT
COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count,
sqlc.embed(oauth2_provider_apps)
FROM oauth2_provider_app_tokens
INNER JOIN oauth2_provider_app_secrets
ON oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id
INNER JOIN oauth2_provider_apps
ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id
INNER JOIN api_keys
ON api_keys.id = oauth2_provider_app_tokens.api_key_id
WHERE
api_keys.user_id = $1
GROUP BY
oauth2_provider_apps.id;
-- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec
DELETE FROM
oauth2_provider_app_tokens
USING
oauth2_provider_app_secrets, api_keys
WHERE
oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id
AND api_keys.id = oauth2_provider_app_tokens.api_key_id
AND oauth2_provider_app_secrets.app_id = $1
AND api_keys.user_id = $2;
+4
View File
@@ -99,4 +99,8 @@ sql:
display_app_ssh_helper: DisplayAppSSHHelper
oauth2_provider_app: OAuth2ProviderApp
oauth2_provider_app_secret: OAuth2ProviderAppSecret
oauth2_provider_app_code: OAuth2ProviderAppCode
oauth2_provider_app_token: OAuth2ProviderAppToken
api_key_id: APIKeyID
callback_url: CallbackURL
login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp
+5 -1
View File
@@ -22,8 +22,12 @@ const (
UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id);
UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt);
UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id);
UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret);
UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id);
UniqueOauth2ProviderAppCodesSecretPrefixKey UniqueConstraint = "oauth2_provider_app_codes_secret_prefix_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_secret_prefix_key UNIQUE (secret_prefix);
UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id);
UniqueOauth2ProviderAppSecretsSecretPrefixKey UniqueConstraint = "oauth2_provider_app_secrets_secret_prefix_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_secret_prefix_key UNIQUE (secret_prefix);
UniqueOauth2ProviderAppTokensHashPrefixKey UniqueConstraint = "oauth2_provider_app_tokens_hash_prefix_key" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_hash_prefix_key UNIQUE (hash_prefix);
UniqueOauth2ProviderAppTokensPkey UniqueConstraint = "oauth2_provider_app_tokens_pkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id);
UniqueOauth2ProviderAppsNameKey UniqueConstraint = "oauth2_provider_apps_name_key" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name);
UniqueOauth2ProviderAppsPkey UniqueConstraint = "oauth2_provider_apps_pkey" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_pkey PRIMARY KEY (id);
UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id);
+32 -9
View File
@@ -23,16 +23,16 @@ type QueryParamParser struct {
// Parsed is a map of all query params that were parsed. This is useful
// for checking if extra query params were passed in.
Parsed map[string]bool
// RequiredParams is a map of all query params that are required. This is useful
// RequiredNotEmptyParams is a map of all query params that are required. This is useful
// for forcing a value to be provided.
RequiredParams map[string]bool
RequiredNotEmptyParams map[string]bool
}
func NewQueryParamParser() *QueryParamParser {
return &QueryParamParser{
Errors: []codersdk.ValidationError{},
Parsed: map[string]bool{},
RequiredParams: map[string]bool{},
Errors: []codersdk.ValidationError{},
Parsed: map[string]bool{},
RequiredNotEmptyParams: map[string]bool{},
}
}
@@ -90,8 +90,10 @@ func (p *QueryParamParser) Boolean(vals url.Values, def bool, queryParam string)
return v
}
func (p *QueryParamParser) Required(queryParam string) *QueryParamParser {
p.RequiredParams[queryParam] = true
func (p *QueryParamParser) RequiredNotEmpty(queryParam ...string) *QueryParamParser {
for _, q := range queryParam {
p.RequiredNotEmptyParams[q] = true
}
return p
}
@@ -121,6 +123,27 @@ func (p *QueryParamParser) UUIDs(vals url.Values, def []uuid.UUID, queryParam st
})
}
func (p *QueryParamParser) RedirectURL(vals url.Values, base *url.URL, queryParam string) *url.URL {
v, err := parseQueryParam(p, vals, url.Parse, base, queryParam)
if err != nil {
p.Errors = append(p.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q must be a valid url: %s", queryParam, err.Error()),
})
}
// It can be a sub-directory but not a sub-domain, as we have apps on
// sub-domains and that seems too dangerous.
if v.Host != base.Host || !strings.HasPrefix(v.Path, base.Path) {
p.Errors = append(p.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q must be a subset of %s", queryParam, base),
})
}
return v
}
func (p *QueryParamParser) Time(vals url.Values, def time.Time, queryParam, layout string) time.Time {
return p.timeWithMutate(vals, def, queryParam, layout, nil)
}
@@ -233,10 +256,10 @@ func ParseCustomList[T any](parser *QueryParamParser, vals url.Values, def []T,
func parseQueryParam[T any](parser *QueryParamParser, vals url.Values, parse func(v string) (T, error), def T, queryParam string) (T, error) {
parser.addParsed(queryParam)
// If the query param is required and not present, return an error.
if parser.RequiredParams[queryParam] && (!vals.Has(queryParam)) {
if parser.RequiredNotEmptyParams[queryParam] && (!vals.Has(queryParam) || vals.Get(queryParam) == "") {
parser.Errors = append(parser.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q is required", queryParam),
Detail: fmt.Sprintf("Query param %q is required and cannot be empty", queryParam),
})
return def, nil
}
+6 -1
View File
@@ -320,9 +320,14 @@ func TestParseQueryParams(t *testing.T) {
t.Parallel()
parser := httpapi.NewQueryParamParser()
parser.Required("test_value")
parser.RequiredNotEmpty("test_value")
parser.UUID(url.Values{}, uuid.New(), "test_value")
require.Len(t, parser.Errors, 1)
parser = httpapi.NewQueryParamParser()
parser.RequiredNotEmpty("test_value")
parser.String(url.Values{"test_value": {""}}, "", "test_value")
require.Len(t, parser.Errors, 1)
})
}
+40 -3
View File
@@ -6,6 +6,8 @@ import (
"net/http"
"reflect"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"golang.org/x/oauth2"
"github.com/coder/coder/v2/coderd/database"
@@ -194,9 +196,44 @@ func ExtractOAuth2ProviderApp(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) {
ctx := r.Context()
appID, ok := ParseUUIDParam(rw, r, "app")
if !ok {
return
// App can come from a URL param, query param, or form value.
paramID := "app"
var appID uuid.UUID
if chi.URLParam(r, paramID) != "" {
var ok bool
appID, ok = ParseUUIDParam(rw, r, "app")
if !ok {
return
}
} else {
// If not provided by the url, then it is provided according to the
// oauth 2 spec. This can occur with query params, or in the body as
// form parameters.
// This also depends on if you are doing a POST (tokens) or GET (authorize).
paramAppID := r.URL.Query().Get("client_id")
if paramAppID == "" {
// Check the form params!
if r.ParseForm() == nil {
paramAppID = r.Form.Get("client_id")
}
}
if paramAppID == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Missing OAuth2 client ID.",
})
return
}
var err error
appID, err = uuid.Parse(paramAppID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid OAuth2 client ID.",
Detail: err.Error(),
})
return
}
}
app, err := db.GetOAuth2ProviderAppByID(ctx, appID)
+6 -6
View File
@@ -72,8 +72,8 @@ func (api *API) insightsUserActivity(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
p := httpapi.NewQueryParamParser().
Required("start_time").
Required("end_time")
RequiredNotEmpty("start_time").
RequiredNotEmpty("end_time")
vals := r.URL.Query()
var (
// The QueryParamParser does not preserve timezone, so we need
@@ -161,8 +161,8 @@ func (api *API) insightsUserLatency(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
p := httpapi.NewQueryParamParser().
Required("start_time").
Required("end_time")
RequiredNotEmpty("start_time").
RequiredNotEmpty("end_time")
vals := r.URL.Query()
var (
// The QueryParamParser does not preserve timezone, so we need
@@ -253,8 +253,8 @@ func (api *API) insightsTemplates(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
p := httpapi.NewQueryParamParser().
Required("start_time").
Required("end_time")
RequiredNotEmpty("start_time").
RequiredNotEmpty("end_time")
vals := r.URL.Query()
var (
// The QueryParamParser does not preserve timezone, so we need
+10 -2
View File
@@ -213,12 +213,20 @@ var (
Type: "oauth2_app",
}
// ResourceOAuth2ProviderAppSecrets CRUD.
// ResourceOAuth2ProviderAppSecret CRUD.
// create/delete = Make or delete an OAuth2 app secret.
// update = Update last used date.
// read = Read OAuth2 app hashed or truncated secret.
ResourceOAuth2ProviderAppSecret = Object{
Type: "oauth2_app_secrets",
Type: "oauth2_app_secret",
}
// ResourceOAuth2ProviderAppCodeToken CRUD.
// create/delete = Make or delete an OAuth2 app code or token.
// update = None
// read = Check if OAuth2 app code or token exists.
ResourceOAuth2ProviderAppCodeToken = Object{
Type: "oauth2_app_code_token",
}
)
+1
View File
@@ -12,6 +12,7 @@ func AllResources() []Object {
ResourceGroup,
ResourceLicense,
ResourceOAuth2ProviderApp,
ResourceOAuth2ProviderAppCodeToken,
ResourceOAuth2ProviderAppSecret,
ResourceOrgRoleAssignment,
ResourceOrganization,
+2
View File
@@ -148,6 +148,8 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ResourceRoleAssignment.Type: {ActionRead},
// All users can see the provisioner daemons.
ResourceProvisionerDaemon.Type: {ActionRead},
// All users can see OAuth2 provider applications.
ResourceOAuth2ProviderApp.Type: {ActionRead},
}),
Org: map[string][]Permission{},
User: append(allPermsExcept(ResourceWorkspaceDormant, ResourceUser, ResourceOrganizationMember),
+1 -1
View File
@@ -583,7 +583,7 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
func (api *API) userAutofillParameters(rw http.ResponseWriter, r *http.Request) {
user := httpmw.UserParam(r)
p := httpapi.NewQueryParamParser().Required("template_id")
p := httpapi.NewQueryParamParser().RequiredNotEmpty("template_id")
templateID := p.UUID(r.URL.Query(), uuid.UUID{}, "template_id")
p.ErrorExcessParams(r.URL.Query())
if len(p.Errors) > 0 {
+1 -1
View File
@@ -636,7 +636,7 @@ func (s *Server) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
values := r.URL.Query()
parser := httpapi.NewQueryParamParser()
reconnect := parser.Required("reconnect").UUID(values, uuid.New(), "reconnect")
reconnect := parser.RequiredNotEmpty("reconnect").UUID(values, uuid.New(), "reconnect")
height := parser.UInt(values, 80, "height")
width := parser.UInt(values, 80, "width")
if len(parser.Errors) > 0 {