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