Commit da156e68 authored by ale's avatar ale

Allow primary-password login for users that have ASPs on other services

parent e1eb24cc
Pipeline #8726 passed with stages
in 1 minute and 46 seconds
......@@ -29,14 +29,19 @@ type AppSpecificPassword struct {
EncryptedPassword []byte
}
// Has2FA returns true if the user supports any 2FA method.
// Has2FA returns true if the user supports any interactive 2FA method.
func (u *User) Has2FA() bool {
return u.HasU2F() || u.HasOTP() || u.HasASPs()
return u.HasU2F() || u.HasOTP()
}
// HasASPs returns true if the user has app-specific passwords.
func (u *User) HasASPs() bool {
return len(u.AppSpecificPasswords) > 0
func (u *User) HasASPs(service string) bool {
for _, asp := range u.AppSpecificPasswords {
if asp.Service == service {
return true
}
}
return false
}
// HasOTP returns true if the user supports (T)OTP.
......
......@@ -386,35 +386,36 @@ fail:
// Authenticate a user. Returning an error should result in an
// AuthResponse with StatusError.
func (s *Server) authenticateUser(req *auth.Request, svc *service, user *backend.User) (resp *auth.Response, err error) {
err = errNoMechanisms
// The service can override the ASP service name presented in
// the request.
aspService := req.Service
if svc.aspService != "" {
aspService = svc.aspService
}
// Verify different credentials depending on whether the user
// has 2FA enabled or not, and on whether the service itself
// supports challenge-response authentication.
err = errNoMechanisms
if svc.ignore2FA {
resp, err = s.authenticateUserWithPassword(user, req)
} else if svc.challengeResponse {
if svc.enforce2FA || user.Has2FA() {
resp, err = s.authenticateUserWith2FA(user, req)
} else if user.HasASPs() {
} else if user.HasASPs(aspService) {
// It doesn't make much sense to support ASPs
// for a challenge-response service, but we
// still do it for completeness.
//
// Rewrite the 'service' for app-specific
// password matching, if necessary.
if svc.aspService != "" {
req.Service = svc.aspService
}
resp, err = s.authenticateUserWithASP(user, req)
resp, err = s.authenticateUserWithASP(user, req, aspService)
} else {
resp, err = s.authenticateUserWithPassword(user, req)
}
} else if svc.enforce2FA || user.HasASPs() {
if svc.aspService != "" {
req.Service = svc.aspService
}
resp, err = s.authenticateUserWithASP(user, req)
} else if svc.enforce2FA || user.HasASPs(aspService) {
resp, err = s.authenticateUserWithASP(user, req, aspService)
} else if !user.Has2FA() {
resp, err = s.authenticateUserWithPassword(user, req)
}
......@@ -452,9 +453,9 @@ func (s *Server) authenticateUserWithPassword(user *backend.User, req *auth.Requ
return nil, errBadPassword
}
func (s *Server) authenticateUserWithASP(user *backend.User, req *auth.Request) (*auth.Response, error) {
func (s *Server) authenticateUserWithASP(user *backend.User, req *auth.Request, aspService string) (*auth.Response, error) {
for _, asp := range user.AppSpecificPasswords {
if asp.Service == req.Service && checkPassword(req.Password, asp.EncryptedPassword) {
if asp.Service == aspService && checkPassword(req.Password, asp.EncryptedPassword) {
return newOK(), nil
}
}
......
......@@ -151,9 +151,9 @@ func runBackendTest(t *testing.T, srv *Server) {
expectGroups []string
expectHasOTP, expectHasU2F, expectHasASPs bool
}{
{"testuser", "interactive", nil, false, false, false},
{"2fauser", "interactive", nil, true, true, false},
{"aspuser", "interactive", nil, false, false, true},
{"testuser", "test", nil, false, false, false},
{"2fauser", "test", nil, true, true, false},
{"aspuser", "test", nil, false, false, true},
}
for _, td := range testdata {
svc, ok := srv.getService(td.service)
......@@ -172,7 +172,7 @@ func runBackendTest(t *testing.T, srv *Server) {
if b := user.HasU2F(); b != td.expectHasU2F {
t.Errorf("user %s has_u2f=%v, expected=%v", td.username, b, td.expectHasU2F)
}
if b := user.HasASPs(); b != td.expectHasASPs {
if b := user.HasASPs(td.service); b != td.expectHasASPs {
t.Errorf("user %s has_asp=%v, expected=%v", td.username, b, td.expectHasASPs)
}
}
......
......@@ -38,6 +38,7 @@ services:
params:
queries:
get_user: "SELECT email, password, totp_secret, '' AS shard FROM users WHERE name = ?"
get_user_u2f: "SELECT public_key, key_handle FROM u2f_registrations WHERE name = ?"
get_user_asp: "SELECT service, password FROM asps WHERE name = ?"
no2fa:
ignore_2fa: true
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment