mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: allow creating manual oidc/github based users (#9000)
* feat: allow creating manual oidc/github based users * Add unit test for oidc and no login type create
This commit is contained in:
Generated
+11
-1
@@ -7536,13 +7536,21 @@ const docTemplate = `{
|
||||
],
|
||||
"properties": {
|
||||
"disable_login": {
|
||||
"description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.",
|
||||
"description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"login_type": {
|
||||
"description": "UserLoginType defaults to LoginTypePassword.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.LoginType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@@ -8449,6 +8457,7 @@ const docTemplate = `{
|
||||
"codersdk.LoginType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"",
|
||||
"password",
|
||||
"github",
|
||||
"oidc",
|
||||
@@ -8456,6 +8465,7 @@ const docTemplate = `{
|
||||
"none"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LoginTypeUnknown",
|
||||
"LoginTypePassword",
|
||||
"LoginTypeGithub",
|
||||
"LoginTypeOIDC",
|
||||
|
||||
Generated
+11
-2
@@ -6715,13 +6715,21 @@
|
||||
"required": ["email", "username"],
|
||||
"properties": {
|
||||
"disable_login": {
|
||||
"description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.",
|
||||
"description": "DisableLogin sets the user's login type to 'none'. This prevents the user\nfrom being able to use a password or any other authentication method to login.\nDeprecated: Set UserLoginType=LoginTypeDisabled instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"login_type": {
|
||||
"description": "UserLoginType defaults to LoginTypePassword.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.LoginType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@@ -7576,8 +7584,9 @@
|
||||
},
|
||||
"codersdk.LoginType": {
|
||||
"type": "string",
|
||||
"enum": ["password", "github", "oidc", "token", "none"],
|
||||
"enum": ["", "password", "github", "oidc", "token", "none"],
|
||||
"x-enum-varnames": [
|
||||
"LoginTypeUnknown",
|
||||
"LoginTypePassword",
|
||||
"LoginTypeGithub",
|
||||
"LoginTypeOIDC",
|
||||
|
||||
@@ -588,14 +588,7 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI
|
||||
require.NoError(t, err)
|
||||
|
||||
var sessionToken string
|
||||
if !req.DisableLogin {
|
||||
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = login.SessionToken
|
||||
} else {
|
||||
if req.DisableLogin || req.UserLoginType == codersdk.LoginTypeNone {
|
||||
// Cannot log in with a disabled login user. So make it an api key from
|
||||
// the client making this user.
|
||||
token, err := client.CreateToken(context.Background(), user.ID.String(), codersdk.CreateTokenRequest{
|
||||
@@ -605,6 +598,13 @@ func createAnotherUserRetry(t *testing.T, client *codersdk.Client, organizationI
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = token.Key
|
||||
} else {
|
||||
login, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
sessionToken = login.SessionToken
|
||||
}
|
||||
|
||||
if user.Status == codersdk.UserStatusDormant {
|
||||
|
||||
+17
-1
@@ -145,7 +145,7 @@ func TestUserLogin(t *testing.T) {
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
// Password auth should fail if the user is made without password login.
|
||||
t.Run("LoginTypeNone", func(t *testing.T) {
|
||||
t.Run("DisableLoginDeprecatedField", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
@@ -160,6 +160,22 @@ func TestUserLogin(t *testing.T) {
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("LoginTypeNone", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequest) {
|
||||
r.Password = ""
|
||||
r.UserLoginType = codersdk.LoginTypeNone
|
||||
})
|
||||
|
||||
_, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{
|
||||
Email: anotherUser.Email,
|
||||
Password: "SomeSecurePassword!",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserAuthMethods(t *testing.T) {
|
||||
|
||||
+29
-11
@@ -287,11 +287,27 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.UserLoginType == "" && req.DisableLogin {
|
||||
// Handle the deprecated field
|
||||
req.UserLoginType = codersdk.LoginTypeNone
|
||||
}
|
||||
if req.UserLoginType == "" {
|
||||
// Default to password auth
|
||||
req.UserLoginType = codersdk.LoginTypePassword
|
||||
}
|
||||
|
||||
if req.UserLoginType != codersdk.LoginTypePassword && req.Password != "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Password cannot be set for non-password (%q) authentication.", req.UserLoginType),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// If password auth is disabled, don't allow new users to be
|
||||
// created with a password!
|
||||
if api.DeploymentValues.DisablePasswordAuth {
|
||||
if api.DeploymentValues.DisablePasswordAuth && req.UserLoginType == codersdk.LoginTypePassword {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: "You cannot manually provision new users with password authentication disabled!",
|
||||
Message: "Password based authentication is disabled! Unable to provision new users with password authentication.",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -353,17 +369,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if req.DisableLogin && req.Password != "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Cannot set password when disabling login.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var loginType database.LoginType
|
||||
if req.DisableLogin {
|
||||
switch req.UserLoginType {
|
||||
case codersdk.LoginTypeNone:
|
||||
loginType = database.LoginTypeNone
|
||||
} else {
|
||||
case codersdk.LoginTypePassword:
|
||||
err = userpassword.Validate(req.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
@@ -376,6 +386,14 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
loginType = database.LoginTypePassword
|
||||
case codersdk.LoginTypeOIDC:
|
||||
loginType = database.LoginTypeOIDC
|
||||
case codersdk.LoginTypeGithub:
|
||||
loginType = database.LoginTypeGithub
|
||||
default:
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Unsupported login type %q for manually creating new users.", req.UserLoginType),
|
||||
})
|
||||
}
|
||||
|
||||
user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -566,6 +567,71 @@ func TestPostUsers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateNoneLoginType", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
first := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: first.OrganizationID,
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypeNone,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := client.User(ctx, user.ID.String())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, found.LoginType, codersdk.LoginTypeNone)
|
||||
})
|
||||
|
||||
t.Run("CreateOIDCLoginType", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
email := "another@user.org"
|
||||
conf := coderdtest.NewOIDCConfig(t, "")
|
||||
config := conf.OIDCConfig(t, jwt.MapClaims{
|
||||
"email": email,
|
||||
})
|
||||
config.AllowSignups = false
|
||||
config.IgnoreUserInfo = true
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
OIDCConfig: config,
|
||||
})
|
||||
first := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
|
||||
OrganizationID: first.OrganizationID,
|
||||
Email: email,
|
||||
Username: "someone-else",
|
||||
Password: "",
|
||||
UserLoginType: codersdk.LoginTypeOIDC,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to log in with OIDC.
|
||||
userClient := codersdk.New(client.URL)
|
||||
resp := oidcCallback(t, userClient, conf.EncodeClaims(t, jwt.MapClaims{
|
||||
"email": email,
|
||||
}))
|
||||
require.Equal(t, resp.StatusCode, http.StatusTemporaryRedirect)
|
||||
// Set the client to use this OIDC context
|
||||
authCookie := authCookieValue(resp.Cookies())
|
||||
userClient.SetSessionToken(authCookie)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
found, err := userClient.User(ctx, "me")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, found.LoginType, codersdk.LoginTypeOIDC)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateUserProfile(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user