forked from mystiq/dex
Merge pull request #138 from joeatwork/disable-users
server: disable users
This commit is contained in:
commit
b19adefde5
9 changed files with 187 additions and 30 deletions
4
db/migrations/0008_users_active_or_inactive.sql
Normal file
4
db/migrations/0008_users_active_or_inactive.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-- +migrate Up
|
||||||
|
ALTER TABLE authd_user ADD COLUMN disabled boolean;
|
||||||
|
|
||||||
|
UPDATE authd_user SET "disabled" = FALSE;
|
File diff suppressed because one or more lines are too long
|
@ -417,6 +417,7 @@ type userModel struct {
|
||||||
Email string `db:"email"`
|
Email string `db:"email"`
|
||||||
EmailVerified bool `db:"email_verified"`
|
EmailVerified bool `db:"email_verified"`
|
||||||
DisplayName string `db:"display_name"`
|
DisplayName string `db:"display_name"`
|
||||||
|
Disabled bool `db:"disabled"`
|
||||||
Admin bool `db:"admin"`
|
Admin bool `db:"admin"`
|
||||||
CreatedAt int64 `db:"created_at"`
|
CreatedAt int64 `db:"created_at"`
|
||||||
}
|
}
|
||||||
|
@ -428,6 +429,7 @@ func (u *userModel) user() (user.User, error) {
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
EmailVerified: u.EmailVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
Admin: u.Admin,
|
Admin: u.Admin,
|
||||||
|
Disabled: u.Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.CreatedAt != 0 {
|
if u.CreatedAt != 0 {
|
||||||
|
@ -444,6 +446,7 @@ func newUserModel(u *user.User) (*userModel, error) {
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
EmailVerified: u.EmailVerified,
|
EmailVerified: u.EmailVerified,
|
||||||
Admin: u.Admin,
|
Admin: u.Admin,
|
||||||
|
Disabled: u.Disabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !u.CreatedAt.IsZero() {
|
if !u.CreatedAt.IsZero() {
|
||||||
|
|
|
@ -53,6 +53,14 @@ var (
|
||||||
Email: "Email-3@example.com",
|
Email: "Email-3@example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
User: user.User{
|
||||||
|
ID: "ID-4",
|
||||||
|
Email: "Email-4@example.com",
|
||||||
|
Admin: true,
|
||||||
|
Disabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
userPasswords = []user.PasswordInfo{
|
userPasswords = []user.PasswordInfo{
|
||||||
|
@ -60,6 +68,10 @@ var (
|
||||||
UserID: "ID-1",
|
UserID: "ID-1",
|
||||||
Password: []byte("hi."),
|
Password: []byte("hi."),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
UserID: "ID-4",
|
||||||
|
Password: []byte("hi."),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
userBadClientID = "ZZZ"
|
userBadClientID = "ZZZ"
|
||||||
|
@ -75,6 +87,9 @@ var (
|
||||||
|
|
||||||
userBadTokenExpired = makeUserToken(testIssuerURL,
|
userBadTokenExpired = makeUserToken(testIssuerURL,
|
||||||
"ID-1", testClientID, time.Hour*-1, testPrivKey)
|
"ID-1", testClientID, time.Hour*-1, testPrivKey)
|
||||||
|
|
||||||
|
userBadTokenDisabled = makeUserToken(testIssuerURL,
|
||||||
|
"ID-4", testClientID, time.Hour*1, testPrivKey)
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeUserAPITestFixtures() *userAPITestFixtures {
|
func makeUserAPITestFixtures() *userAPITestFixtures {
|
||||||
|
@ -166,6 +181,11 @@ func TestGetUser(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
id: "ID-1",
|
id: "ID-1",
|
||||||
|
|
||||||
|
token: userBadTokenDisabled,
|
||||||
|
errCode: http.StatusUnauthorized,
|
||||||
|
}, {
|
||||||
|
id: "ID-1",
|
||||||
|
|
||||||
token: "",
|
token: "",
|
||||||
errCode: http.StatusUnauthorized,
|
errCode: http.StatusUnauthorized,
|
||||||
}, {
|
}, {
|
||||||
|
@ -229,20 +249,28 @@ func TestListUsers(t *testing.T) {
|
||||||
wantIDs [][]string
|
wantIDs [][]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
pages: 3,
|
pages: 4,
|
||||||
maxResults: 1,
|
maxResults: 1,
|
||||||
|
|
||||||
token: userGoodToken,
|
token: userGoodToken,
|
||||||
|
|
||||||
wantIDs: [][]string{{"ID-1"}, {"ID-2"}, {"ID-3"}},
|
wantIDs: [][]string{{"ID-1"}, {"ID-2"}, {"ID-3"}, {"ID-4"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pages: 1,
|
pages: 1,
|
||||||
|
|
||||||
token: userGoodToken,
|
token: userGoodToken,
|
||||||
|
|
||||||
maxResults: 3,
|
maxResults: 4,
|
||||||
wantIDs: [][]string{{"ID-1", "ID-2", "ID-3"}},
|
wantIDs: [][]string{{"ID-1", "ID-2", "ID-3", "ID-4"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pages: 1,
|
||||||
|
|
||||||
|
token: userBadTokenDisabled,
|
||||||
|
|
||||||
|
maxResults: 1,
|
||||||
|
wantCode: http.StatusUnauthorized, // TODO don't merge until you're sure this is covering what you expect
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pages: 3,
|
pages: 3,
|
||||||
|
@ -417,6 +445,22 @@ func TestCreateUser(t *testing.T) {
|
||||||
// try every variation like in TestGetUser
|
// try every variation like in TestGetUser
|
||||||
token: userBadTokenExpired,
|
token: userBadTokenExpired,
|
||||||
|
|
||||||
|
wantCode: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
req: schema.UserCreateRequest{
|
||||||
|
User: &schema.User{
|
||||||
|
Email: "newuser@example.com",
|
||||||
|
DisplayName: "New User",
|
||||||
|
EmailVerified: true,
|
||||||
|
Admin: false,
|
||||||
|
CreatedAt: clock.Now().Format(time.RFC3339),
|
||||||
|
},
|
||||||
|
RedirectURL: testRedirectURL.String(),
|
||||||
|
},
|
||||||
|
|
||||||
|
token: userBadTokenDisabled,
|
||||||
|
|
||||||
wantCode: http.StatusUnauthorized,
|
wantCode: http.StatusUnauthorized,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,6 +326,10 @@ func (s *Server) Login(ident oidc.Identity, key string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if usr.Disabled {
|
||||||
|
return "", user.ErrorNotFound
|
||||||
|
}
|
||||||
|
|
||||||
ses, err = s.SessionManager.AttachUser(sessionID, usr.ID)
|
ses, err = s.SessionManager.AttachUser(sessionID, usr.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -261,6 +261,74 @@ func TestServerLoginUnrecognizedSessionKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerLoginDisabledUser(t *testing.T) {
|
||||||
|
ci := oidc.ClientIdentity{
|
||||||
|
Credentials: oidc.ClientCredentials{
|
||||||
|
ID: "XXX",
|
||||||
|
Secret: "secrete",
|
||||||
|
},
|
||||||
|
Metadata: oidc.ClientMetadata{
|
||||||
|
RedirectURLs: []url.URL{
|
||||||
|
url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "client.example.com",
|
||||||
|
Path: "/callback",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ciRepo := client.NewClientIdentityRepo([]oidc.ClientIdentity{ci})
|
||||||
|
|
||||||
|
km := &StaticKeyManager{
|
||||||
|
signer: &StaticSigner{sig: []byte("beer"), err: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm := session.NewSessionManager(session.NewSessionRepo(), session.NewSessionKeyRepo())
|
||||||
|
sm.GenerateCode = staticGenerateCodeFunc("fakecode")
|
||||||
|
sessionID, err := sm.NewSession("test_connector_id", ci.Credentials.ID, "bogus", ci.Metadata.RedirectURLs[0], "", false, []string{"openid"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userRepo, err := makeNewUserRepo()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userRepo.Create(nil, user.User{
|
||||||
|
ID: "disabled-1",
|
||||||
|
Email: "disabled@example.com",
|
||||||
|
Disabled: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userRepo.AddRemoteIdentity(nil, "disabled-1", user.RemoteIdentity{
|
||||||
|
ConnectorID: "test_connector_id",
|
||||||
|
ID: "disabled-connector-id",
|
||||||
|
})
|
||||||
|
|
||||||
|
srv := &Server{
|
||||||
|
IssuerURL: url.URL{Scheme: "http", Host: "server.example.com"},
|
||||||
|
KeyManager: km,
|
||||||
|
SessionManager: sm,
|
||||||
|
ClientIdentityRepo: ciRepo,
|
||||||
|
UserRepo: userRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
ident := oidc.Identity{ID: "disabled-connector-id", Name: "elroy", Email: "elroy@example.com"}
|
||||||
|
key, err := sm.NewSessionKey(sessionID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = srv.Login(ident, key)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("disabled user was allowed to log in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServerCodeToken(t *testing.T) {
|
func TestServerCodeToken(t *testing.T) {
|
||||||
ci := oidc.ClientIdentity{
|
ci := oidc.ClientIdentity{
|
||||||
Credentials: oidc.ClientCredentials{
|
Credentials: oidc.ClientCredentials{
|
||||||
|
|
|
@ -197,7 +197,7 @@ func (u *UsersAPI) ListUsers(creds Creds, maxResults int, nextPageToken string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsersAPI) Authorize(creds Creds) bool {
|
func (u *UsersAPI) Authorize(creds Creds) bool {
|
||||||
return creds.User.Admin
|
return creds.User.Admin && !creds.User.Disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func userToSchemaUser(usr user.User) schema.User {
|
func userToSchemaUser(usr user.User) schema.User {
|
||||||
|
|
|
@ -52,6 +52,15 @@ var (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disabledCreds = Creds{
|
||||||
|
User: user.User{
|
||||||
|
ID: "ID-1",
|
||||||
|
Admin: true,
|
||||||
|
Disabled: true,
|
||||||
|
},
|
||||||
|
ClientID: "XXX",
|
||||||
|
}
|
||||||
|
|
||||||
resetPasswordURL = url.URL{
|
resetPasswordURL = url.URL{
|
||||||
Host: "dex.example.com",
|
Host: "dex.example.com",
|
||||||
Path: "resetPassword",
|
Path: "resetPassword",
|
||||||
|
|
|
@ -41,6 +41,8 @@ type User struct {
|
||||||
|
|
||||||
Admin bool
|
Admin bool
|
||||||
|
|
||||||
|
Disabled bool
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue