Commit 589dd889 authored by ale's avatar ale

Return all supported 2FA mechanisms in the authentication response

parent e29f41a8
Pipeline #2829 passed with stages
in 1 minute and 24 seconds
...@@ -58,7 +58,6 @@ type Request struct { ...@@ -58,7 +58,6 @@ type Request struct {
U2FAppID string U2FAppID string
U2FResponse *u2f.SignResponse U2FResponse *u2f.SignResponse
DeviceInfo *DeviceInfo DeviceInfo *DeviceInfo
//Extra map[string]string
} }
func (r *Request) EncodeToMap(m map[string]string, prefix string) { func (r *Request) EncodeToMap(m map[string]string, prefix string) {
...@@ -98,6 +97,25 @@ type UserInfo struct { ...@@ -98,6 +97,25 @@ type UserInfo struct {
Groups []string Groups []string
} }
func encodeStringList(m map[string]string, prefix string, l []string) {
for i, elem := range l {
m[fmt.Sprintf("%s.%d.", prefix, i)] = elem
}
}
func decodeStringList(m map[string]string, prefix string) (out []string) {
i := 0
for {
s, ok := m[fmt.Sprintf("%s.%d.", prefix, i)]
if !ok {
break
}
out = append(out, s)
i++
}
return
}
func (u *UserInfo) EncodeToMap(m map[string]string, prefix string) { func (u *UserInfo) EncodeToMap(m map[string]string, prefix string) {
if u.Email != "" { if u.Email != "" {
m[prefix+"email"] = u.Email m[prefix+"email"] = u.Email
...@@ -105,24 +123,14 @@ func (u *UserInfo) EncodeToMap(m map[string]string, prefix string) { ...@@ -105,24 +123,14 @@ func (u *UserInfo) EncodeToMap(m map[string]string, prefix string) {
if u.Shard != "" { if u.Shard != "" {
m[prefix+"shard"] = u.Shard m[prefix+"shard"] = u.Shard
} }
for i, g := range u.Groups { encodeStringList(m, prefix+"group", u.Groups)
m[fmt.Sprintf("%sgroup.%d.", prefix, i)] = g
}
} }
func decodeUserInfoFromMap(m map[string]string, prefix string) *UserInfo { func decodeUserInfoFromMap(m map[string]string, prefix string) *UserInfo {
u := UserInfo{ u := UserInfo{
Email: m[prefix+"email"], Email: m[prefix+"email"],
Shard: m[prefix+"shard"], Shard: m[prefix+"shard"],
} Groups: decodeStringList(m, prefix+"group"),
i := 0
for {
s, ok := m[fmt.Sprintf("%sgroup.%d.", prefix, i)]
if !ok {
break
}
u.Groups = append(u.Groups, s)
i++
} }
if u.Email == "" && u.Shard == "" && len(u.Groups) == 0 { if u.Email == "" && u.Shard == "" && len(u.Groups) == 0 {
return nil return nil
...@@ -133,16 +141,38 @@ func decodeUserInfoFromMap(m map[string]string, prefix string) *UserInfo { ...@@ -133,16 +141,38 @@ func decodeUserInfoFromMap(m map[string]string, prefix string) *UserInfo {
// Response to an authentication request. // Response to an authentication request.
type Response struct { type Response struct {
Status Status Status Status
TFAMethod TFAMethod TFAMethods []TFAMethod
U2FSignRequest *u2f.WebSignRequest U2FSignRequest *u2f.WebSignRequest
UserInfo *UserInfo UserInfo *UserInfo
} }
func encodeTFAMethodList(m map[string]string, prefix string, l []TFAMethod) {
if len(l) == 0 {
return
}
tmp := make([]string, 0, len(l))
for _, el := range l {
tmp = append(tmp, string(el))
}
encodeStringList(m, prefix, tmp)
}
func decodeTFAMethodList(m map[string]string, prefix string) []TFAMethod {
l := decodeStringList(m, prefix)
if len(l) == 0 {
return nil
}
out := make([]TFAMethod, 0, len(l))
for _, el := range l {
out = append(out, TFAMethod(el))
}
return out
}
func (r *Response) EncodeToMap(m map[string]string, prefix string) { func (r *Response) EncodeToMap(m map[string]string, prefix string) {
m[prefix+"status"] = r.Status.String() m[prefix+"status"] = r.Status.String()
m[prefix+"2fa_method"] = string(r.TFAMethod) encodeTFAMethodList(m, prefix+"2fa_methods", r.TFAMethods)
if r.U2FSignRequest != nil { if r.U2FSignRequest != nil {
// External type.
encodeU2FSignRequestToMap(r.U2FSignRequest, m, prefix+"u2f_req.") encodeU2FSignRequestToMap(r.U2FSignRequest, m, prefix+"u2f_req.")
} }
if r.UserInfo != nil { if r.UserInfo != nil {
...@@ -152,7 +182,7 @@ func (r *Response) EncodeToMap(m map[string]string, prefix string) { ...@@ -152,7 +182,7 @@ func (r *Response) EncodeToMap(m map[string]string, prefix string) {
func (r *Response) DecodeFromMap(m map[string]string, prefix string) { func (r *Response) DecodeFromMap(m map[string]string, prefix string) {
r.Status = parseAuthStatus(m[prefix+"status"]) r.Status = parseAuthStatus(m[prefix+"status"])
r.TFAMethod = TFAMethod(m[prefix+"2fa_method"]) r.TFAMethods = decodeTFAMethodList(m, prefix+"2fa_methods")
r.U2FSignRequest = decodeU2FSignRequestFromMap(m, prefix+"u2f_req.") r.U2FSignRequest = decodeU2FSignRequestFromMap(m, prefix+"u2f_req.")
r.UserInfo = decodeUserInfoFromMap(m, prefix+"user.") r.UserInfo = decodeUserInfoFromMap(m, prefix+"user.")
} }
......
...@@ -51,8 +51,8 @@ func TestProtocol_SerializeResponse(t *testing.T) { ...@@ -51,8 +51,8 @@ func TestProtocol_SerializeResponse(t *testing.T) {
c := &kvCodec{} c := &kvCodec{}
resp := &Response{ resp := &Response{
Status: StatusInsufficientCredentials, Status: StatusInsufficientCredentials,
TFAMethod: TFAMethodU2F, TFAMethods: []TFAMethod{TFAMethodU2F},
U2FSignRequest: &u2f.WebSignRequest{ U2FSignRequest: &u2f.WebSignRequest{
AppID: "https://some-app-id", AppID: "https://some-app-id",
Challenge: "u2fChallenge", Challenge: "u2fChallenge",
......
...@@ -435,15 +435,18 @@ func (s *Server) authenticateUserWith2FA(user *backend.User, req *auth.Request) ...@@ -435,15 +435,18 @@ func (s *Server) authenticateUserWith2FA(user *backend.User, req *auth.Request)
resp := &auth.Response{ resp := &auth.Response{
Status: auth.StatusInsufficientCredentials, Status: auth.StatusInsufficientCredentials,
} }
// Two-factor mechanisms are returned in order of
// decreasing preference, so start with U2F.
if req.U2FAppID != "" && user.HasU2F() { if req.U2FAppID != "" && user.HasU2F() {
resp.TFAMethod = auth.TFAMethodU2F resp.TFAMethods = append(resp.TFAMethods, auth.TFAMethodU2F)
signReq, err := s.u2fSignRequest(user, req.U2FAppID) signReq, err := s.u2fSignRequest(user, req.U2FAppID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp.U2FSignRequest = signReq resp.U2FSignRequest = signReq
} else if user.HasOTP() { }
resp.TFAMethod = auth.TFAMethodOTP if user.HasOTP() {
resp.TFAMethods = append(resp.TFAMethods, auth.TFAMethodOTP)
} }
return resp, nil return resp, nil
} }
......
...@@ -171,8 +171,17 @@ func runAuthenticationTest(t *testing.T, client client.Client) { ...@@ -171,8 +171,17 @@ func runAuthenticationTest(t *testing.T, client client.Client) {
if resp.Status != td.expectedStatus { if resp.Status != td.expectedStatus {
t.Errorf("authentication error: s=interactive u=%s p=%s, expected=%v got=%v", td.username, td.password, td.expectedStatus, resp.Status) t.Errorf("authentication error: s=interactive u=%s p=%s, expected=%v got=%v", td.username, td.password, td.expectedStatus, resp.Status)
} }
if resp.TFAMethod != td.expectedTFAMethod { if td.expectedTFAMethod != auth.TFAMethodNone {
t.Errorf("mismatch in TFAMethod hint in authentication response: s=interactive u=%s p=%s, expected=%v got=%v", td.username, td.password, td.expectedTFAMethod, resp.TFAMethod) found := false
for _, m := range resp.TFAMethods {
if m == td.expectedTFAMethod {
found = true
break
}
}
if !found {
t.Errorf("mismatch in TFAMethod hint in authentication response: s=interactive u=%s p=%s, expected=%v got=%v", td.username, td.password, td.expectedTFAMethod, resp.TFAMethods)
}
} }
} }
} }
......
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