-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examplebroker: Simplify auth modes handling by using a maps of structs
We were using a maps of maps that made code hard to read and maintain and doing some unneeded conversions to json back and forth. Avoid this by just using structured data instead
- Loading branch information
Showing
1 changed file
with
51 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import ( | |
"errors" | ||
"fmt" | ||
"html/template" | ||
"maps" | ||
"math" | ||
"os" | ||
"sort" | ||
|
@@ -55,20 +56,21 @@ const ( | |
webViewID = "webview" | ||
) | ||
|
||
const ( | ||
ui = "ui" | ||
selectionLabel = "selection_label" | ||
phone = "phone" | ||
wantedCode = "wantedCode" | ||
) | ||
type authMode struct { | ||
selectionLabel string | ||
ui map[string]string | ||
email string | ||
phone string | ||
wantedCode string | ||
} | ||
|
||
type sessionInfo struct { | ||
username string | ||
lang string | ||
sessionMode string | ||
|
||
currentAuthMode string | ||
allModes map[string]map[string]string | ||
allModes map[string]authMode | ||
attemptsPerMode map[string]int | ||
|
||
pwdChange passwdReset | ||
|
@@ -279,7 +281,7 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s | |
authMode := allModes[id] | ||
authenticationModes = append(authenticationModes, map[string]string{ | ||
layouts.ID: id, | ||
layouts.Label: authMode[selectionLabel], | ||
layouts.Label: authMode.selectionLabel, | ||
}) | ||
} | ||
log.Debugf(ctx, "Supported authentication modes for %s: %#v", sessionID, allModes) | ||
|
@@ -292,101 +294,101 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s | |
return authenticationModes, nil | ||
} | ||
|
||
func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]string) map[string]map[string]string { | ||
allModes := make(map[string]map[string]string) | ||
func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]string) map[string]authMode { | ||
allModes := make(map[string]authMode) | ||
for _, layout := range supportedUILayouts { | ||
switch layout[layouts.Type] { | ||
case layouts.Form: | ||
if layout[layouts.Entry] != "" { | ||
_, supportedEntries := layouts.ParseItems(layout["entry"]) | ||
if slices.Contains(supportedEntries, entries.CharsPassword) { | ||
allModes[passwordID] = map[string]string{ | ||
allModes[passwordID] = authMode{ | ||
selectionLabel: "Password authentication", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Gimme your password", | ||
layouts.Entry: entries.CharsPassword, | ||
}), | ||
}, | ||
} | ||
} | ||
if slices.Contains(supportedEntries, entries.Digits) { | ||
allModes[pinCodeID] = map[string]string{ | ||
allModes[pinCodeID] = authMode{ | ||
selectionLabel: "Pin code", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Enter your pin code", | ||
layouts.Entry: entries.Digits, | ||
}), | ||
}, | ||
} | ||
} | ||
if slices.Contains(supportedEntries, entries.Chars) && layout[layouts.Wait] != "" { | ||
allModes[fmt.Sprintf("entry_or_wait_for_%s_gmail.com", sessionInfo.username)] = map[string]string{ | ||
allModes[fmt.Sprintf("entry_or_wait_for_%s_gmail.com", sessionInfo.username)] = authMode{ | ||
selectionLabel: fmt.Sprintf("Send URL to %[email protected]", sessionInfo.username), | ||
"email": fmt.Sprintf("%[email protected]", sessionInfo.username), | ||
ui: mapToJSON(map[string]string{ | ||
email: fmt.Sprintf("%[email protected]", sessionInfo.username), | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: fmt.Sprintf("Click on the link received at %[email protected] or enter the code:", sessionInfo.username), | ||
layouts.Entry: entries.Chars, | ||
layouts.Wait: layouts.True, | ||
}), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
// The broker could parse the values, that are either true/false | ||
if layout[layouts.Wait] != "" { | ||
if layout[layouts.Button] == layouts.Optional { | ||
allModes[totpWithButtonID] = map[string]string{ | ||
allModes[totpWithButtonID] = authMode{ | ||
selectionLabel: "Authentication code", | ||
phone: "+33…", | ||
wantedCode: "temporary pass", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Enter your one time credential", | ||
layouts.Entry: entries.Chars, | ||
layouts.Button: "Resend sms", | ||
}), | ||
}, | ||
} | ||
} else { | ||
allModes[totpID] = map[string]string{ | ||
allModes[totpID] = authMode{ | ||
selectionLabel: "Authentication code", | ||
phone: "+33…", | ||
wantedCode: "temporary pass", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Enter your one time credential", | ||
layouts.Entry: entries.Chars, | ||
}), | ||
}, | ||
} | ||
} | ||
|
||
allModes[phoneAck1ID] = map[string]string{ | ||
allModes[phoneAck1ID] = authMode{ | ||
selectionLabel: "Use your phone +33…", | ||
phone: "+33…", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Unlock your phone +33… or accept request on web interface:", | ||
layouts.Wait: layouts.True, | ||
}), | ||
}, | ||
} | ||
|
||
allModes[phoneAck2ID] = map[string]string{ | ||
allModes[phoneAck2ID] = authMode{ | ||
selectionLabel: "Use your phone +1…", | ||
phone: "+1…", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Unlock your phone +1… or accept request on web interface", | ||
layouts.Wait: layouts.True, | ||
}), | ||
}, | ||
} | ||
|
||
allModes[fidoDevice1ID] = map[string]string{ | ||
allModes[fidoDevice1ID] = authMode{ | ||
selectionLabel: "Use your fido device foo", | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.Form, | ||
layouts.Label: "Plug your fido device and press with your thumb", | ||
layouts.Wait: layouts.True, | ||
}), | ||
}, | ||
} | ||
} | ||
|
||
|
@@ -403,14 +405,14 @@ func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string] | |
modeSelectionLabel = "Use a Login code" | ||
modeLabel = "Enter the code in the login page" | ||
} | ||
allModes[modeName] = map[string]string{ | ||
allModes[modeName] = authMode{ | ||
selectionLabel: modeSelectionLabel, | ||
ui: mapToJSON(map[string]string{ | ||
ui: map[string]string{ | ||
layouts.Type: layouts.QrCode, | ||
layouts.Label: modeLabel, | ||
layouts.Wait: layouts.True, | ||
layouts.Button: "Regenerate code", | ||
}), | ||
}, | ||
} | ||
|
||
case webViewID: | ||
|
@@ -421,8 +423,8 @@ func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string] | |
return allModes | ||
} | ||
|
||
func getMfaModes(info sessionInfo, supportedModes map[string]map[string]string) map[string]map[string]string { | ||
mfaModes := make(map[string]map[string]string) | ||
func getMfaModes(info sessionInfo, supportedModes map[string]authMode) map[string]authMode { | ||
mfaModes := make(map[string]authMode) | ||
for _, mode := range []string{phoneAck1ID, totpWithButtonID, fidoDevice1ID} { | ||
if _, exists := supportedModes[mode]; exists && info.currentAuthMode != mode { | ||
mfaModes[mode] = supportedModes[mode] | ||
|
@@ -431,8 +433,8 @@ func getMfaModes(info sessionInfo, supportedModes map[string]map[string]string) | |
return mfaModes | ||
} | ||
|
||
func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]string) map[string]map[string]string { | ||
passwdResetModes := make(map[string]map[string]string) | ||
func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]string) map[string]authMode { | ||
passwdResetModes := make(map[string]authMode) | ||
for _, layout := range supportedUILayouts { | ||
if layout[layouts.Type] != layouts.NewPassword { | ||
continue | ||
|
@@ -454,9 +456,9 @@ func getPasswdResetModes(info sessionInfo, supportedUILayouts []map[string]strin | |
uiMap[layouts.Button] = "Skip" | ||
} | ||
|
||
passwdResetModes[mode] = map[string]string{ | ||
passwdResetModes[mode] = authMode{ | ||
selectionLabel: "Password reset", | ||
ui: mapToJSON(uiMap), | ||
ui: uiMap, | ||
} | ||
} | ||
return passwdResetModes | ||
|
@@ -494,17 +496,17 @@ func (b *Broker) SelectAuthenticationMode(ctx context.Context, sessionID, authen | |
} | ||
|
||
// populate UI options based on selected authentication mode | ||
uiLayoutInfo = jsonToMap(authenticationMode[ui]) | ||
uiLayoutInfo = maps.Clone(authenticationMode.ui) | ||
|
||
// The broker does extra "out of bound" connections when needed | ||
switch authenticationModeName { | ||
case totpWithButtonID, totpID: | ||
// send sms to sessionInfo.allModes[authenticationModeName][phone] | ||
// send sms to sessionInfo.allModes[authenticationModeName].phone | ||
// add a 0 to simulate new code generation. | ||
authenticationMode[wantedCode] = authenticationMode[wantedCode] + "0" | ||
authenticationMode.wantedCode += "0" | ||
sessionInfo.allModes[authenticationModeName] = authenticationMode | ||
case phoneAck1ID, phoneAck2ID: | ||
// send request to sessionInfo.allModes[authenticationModeName][phone] | ||
// send request to sessionInfo.allModes[authenticationModeName].phone | ||
case fidoDevice1ID: | ||
// start transaction with fido device | ||
case qrCodeAndCodeID, codeID: | ||
|
@@ -624,7 +626,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, sessionInfo sessionI | |
} | ||
|
||
case totpWithButtonID, totpID: | ||
wantedCode := sessionInfo.allModes[sessionInfo.currentAuthMode][wantedCode] | ||
wantedCode := sessionInfo.allModes[sessionInfo.currentAuthMode].wantedCode | ||
if challenge != wantedCode { | ||
return auth.Retry, `{"message": "invalid totp code"}` | ||
} | ||
|
@@ -787,22 +789,6 @@ func (b *Broker) UserPreCheck(ctx context.Context, username string) (string, err | |
return userInfoFromName(username), nil | ||
} | ||
|
||
func mapToJSON(input map[string]string) string { | ||
data, err := json.Marshal(input) | ||
if err != nil { | ||
panic(fmt.Sprintf("Invalid map data: %v", err)) | ||
} | ||
return string(data) | ||
} | ||
|
||
func jsonToMap(data string) map[string]string { | ||
r := make(map[string]string) | ||
if err := json.Unmarshal([]byte(data), &r); err != nil { | ||
panic(fmt.Sprintf("Invalid map data: %v", err)) | ||
} | ||
return r | ||
} | ||
|
||
// decryptAES is just here to illustrate the encryption and decryption | ||
// and in no way the right way to perform a secure encryption | ||
// | ||
|