|
@@ -1,329 +1,200 @@
|
|
|
package sso
|
|
|
|
|
|
+/*
|
|
|
+ handlers.go
|
|
|
+
|
|
|
+ This file contains the handlers for the SSO module.
|
|
|
+ If you are looking for handlers for SSO user management,
|
|
|
+ please refer to userHandlers.go.
|
|
|
+*/
|
|
|
+
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
- "errors"
|
|
|
"net/http"
|
|
|
+ "strings"
|
|
|
|
|
|
"github.com/gofrs/uuid"
|
|
|
- "imuslab.com/zoraxy/mod/auth"
|
|
|
"imuslab.com/zoraxy/mod/utils"
|
|
|
)
|
|
|
|
|
|
-/* Handlers for SSO user management */
|
|
|
-
|
|
|
-// HandleAddUser handle the request to add a new user to the SSO system
|
|
|
-func (s *SSOHandler) HandleAddUser(w http.ResponseWriter, r *http.Request) {
|
|
|
- username, err := utils.PostPara(r, "username")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid username given")
|
|
|
- return
|
|
|
+// HandleSSOStatus handle the request to get the status of the SSO portal server
|
|
|
+func (s *SSOHandler) HandleSSOStatus(w http.ResponseWriter, r *http.Request) {
|
|
|
+ type SSOStatus struct {
|
|
|
+ Enabled bool
|
|
|
+ SSOInterceptEnabled bool
|
|
|
+ ListeningPort int
|
|
|
+ AuthURL string
|
|
|
}
|
|
|
|
|
|
- password, err := utils.PostPara(r, "password")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid password given")
|
|
|
- return
|
|
|
+ status := SSOStatus{
|
|
|
+ Enabled: s.ssoPortalServer != nil,
|
|
|
+ //SSOInterceptEnabled: s.ssoInterceptEnabled,
|
|
|
+ ListeningPort: s.Config.PortalServerPort,
|
|
|
+ AuthURL: s.Config.AuthURL,
|
|
|
}
|
|
|
|
|
|
- newUserId, err := uuid.NewV4()
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to generate new user ID")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- //Create a new user entry
|
|
|
- thisUserEntry := UserEntry{
|
|
|
- UserID: newUserId.String(),
|
|
|
- Username: username,
|
|
|
- PasswordHash: auth.Hash(password),
|
|
|
- TOTPCode: "",
|
|
|
- Enable2FA: false,
|
|
|
- }
|
|
|
-
|
|
|
- js, _ := json.Marshal(thisUserEntry)
|
|
|
-
|
|
|
- //Create a new user in the database
|
|
|
- err = s.Config.Database.Write("sso_users", newUserId.String(), string(js))
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to create new user")
|
|
|
- return
|
|
|
- }
|
|
|
- utils.SendOK(w)
|
|
|
+ js, _ := json.Marshal(status)
|
|
|
+ utils.SendJSONResponse(w, string(js))
|
|
|
}
|
|
|
|
|
|
-// Edit user information, only accept change of username, password and enabled subdomain filed
|
|
|
-func (s *SSOHandler) HandleEditUser(w http.ResponseWriter, r *http.Request) {
|
|
|
- userID, err := utils.PostPara(r, "user_id")
|
|
|
+// HandleStartSSOPortal handle the request to start the SSO portal server
|
|
|
+func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
|
|
|
+ err := s.StartSSOPortal()
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if !(s.SSO_UserExists(userID)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
+ s.Log("Failed to start SSO portal server", err)
|
|
|
+ utils.SendErrorResponse(w, "failed to start SSO portal server")
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- //Load the user entry from database
|
|
|
- userEntry, err := s.SSO_GetUser(userID)
|
|
|
+ //Write current state to database
|
|
|
+ err = s.Config.Database.Write("sso_conf", "enabled", true)
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- //Update each of the fields if it is provided
|
|
|
- username, err := utils.PostPara(r, "username")
|
|
|
- if err == nil {
|
|
|
- userEntry.Username = username
|
|
|
- }
|
|
|
-
|
|
|
- password, err := utils.PostPara(r, "password")
|
|
|
- if err == nil {
|
|
|
- userEntry.PasswordHash = auth.Hash(password)
|
|
|
- }
|
|
|
-
|
|
|
- //Update the user entry in the database
|
|
|
- js, _ := json.Marshal(userEntry)
|
|
|
- err = s.Config.Database.Write("sso_users", userID, string(js))
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to update user entry")
|
|
|
+ utils.SendErrorResponse(w, "failed to update SSO state")
|
|
|
return
|
|
|
}
|
|
|
utils.SendOK(w)
|
|
|
}
|
|
|
|
|
|
-// HandleRemoveUser remove a user from the SSO system
|
|
|
-func (s *SSOHandler) HandleRemoveUser(w http.ResponseWriter, r *http.Request) {
|
|
|
- userID, err := utils.PostPara(r, "user_id")
|
|
|
+// HandleStopSSOPortal handle the request to stop the SSO portal server
|
|
|
+func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
|
|
|
+ err := s.ssoPortalServer.Close()
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if !(s.SSO_UserExists(userID)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
+ s.Log("Failed to stop SSO portal server", err)
|
|
|
+ utils.SendErrorResponse(w, "failed to stop SSO portal server")
|
|
|
return
|
|
|
}
|
|
|
+ s.ssoPortalServer = nil
|
|
|
|
|
|
- //Remove the user from the database
|
|
|
- err = s.Config.Database.Delete("sso_users", userID)
|
|
|
+ //Write current state to database
|
|
|
+ err = s.Config.Database.Write("sso_conf", "enabled", false)
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to remove user")
|
|
|
+ utils.SendErrorResponse(w, "failed to update SSO state")
|
|
|
return
|
|
|
}
|
|
|
- utils.SendOK(w)
|
|
|
-}
|
|
|
|
|
|
-// HandleListUser list all users in the SSO system
|
|
|
-func (s *SSOHandler) HandleListUser(w http.ResponseWriter, r *http.Request) {
|
|
|
- entries, err := s.Config.Database.ListTable("sso_users")
|
|
|
+ //Clear the cookie store and restart the server
|
|
|
+ err = s.RestartSSOServer()
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to list users")
|
|
|
+ utils.SendErrorResponse(w, "failed to restart SSO server")
|
|
|
return
|
|
|
}
|
|
|
- ssoUsers := map[string]*UserEntry{}
|
|
|
- for _, keypairs := range entries {
|
|
|
- userid := string(keypairs[0])
|
|
|
- group := new(UserEntry)
|
|
|
- json.Unmarshal(keypairs[1], &group)
|
|
|
- ssoUsers[userid] = group
|
|
|
- }
|
|
|
-
|
|
|
- js, _ := json.Marshal(ssoUsers)
|
|
|
- utils.SendJSONResponse(w, string(js))
|
|
|
+ utils.SendOK(w)
|
|
|
}
|
|
|
|
|
|
-func (s *SSOHandler) HandleAddSubdomain(w http.ResponseWriter, r *http.Request) {
|
|
|
- userid, err := utils.PostPara(r, "user_id")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if !(s.SSO_UserExists(userid)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
+// HandlePortChange handle the request to change the SSO portal server port
|
|
|
+func (s *SSOHandler) HandlePortChange(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.Method == http.MethodGet {
|
|
|
+ //Return the current port
|
|
|
+ js, _ := json.Marshal(s.Config.PortalServerPort)
|
|
|
+ utils.SendJSONResponse(w, string(js))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- UserEntry, err := s.SSO_GetUser(userid)
|
|
|
+ port, err := utils.PostInt(r, "port")
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
+ utils.SendErrorResponse(w, "invalid port given")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- subdomain, err := utils.PostPara(r, "subdomain")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid subdomain given")
|
|
|
- return
|
|
|
- }
|
|
|
+ s.Config.PortalServerPort = port
|
|
|
|
|
|
- allowAccess, err := utils.PostBool(r, "allow_access")
|
|
|
+ //Write to the database
|
|
|
+ err = s.Config.Database.Write("sso_conf", "port", port)
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid allow access value given")
|
|
|
+ utils.SendErrorResponse(w, "failed to update port")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- UserEntry.Subdomains[subdomain] = &SubdomainAccessRule{
|
|
|
- Subdomain: subdomain,
|
|
|
- AllowAccess: allowAccess,
|
|
|
- }
|
|
|
-
|
|
|
- err = UserEntry.Update()
|
|
|
+ //Clear the cookie store and restart the server
|
|
|
+ err = s.RestartSSOServer()
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to update user entry")
|
|
|
+ utils.SendErrorResponse(w, "failed to restart SSO server")
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
utils.SendOK(w)
|
|
|
}
|
|
|
|
|
|
-func (s *SSOHandler) HandleRemoveSubdomain(w http.ResponseWriter, r *http.Request) {
|
|
|
- userid, err := utils.PostPara(r, "user_id")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
+// HandleSetAuthURL handle the request to change the SSO auth URL
|
|
|
+// This is the URL that the SSO portal server will redirect to for authentication
|
|
|
+// e.g. auth.yourdomain.com
|
|
|
+func (s *SSOHandler) HandleSetAuthURL(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.Method == http.MethodGet {
|
|
|
+ //Return the current auth URL
|
|
|
+ js, _ := json.Marshal(s.Config.AuthURL)
|
|
|
+ utils.SendJSONResponse(w, string(js))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if !(s.SSO_UserExists(userid)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- UserEntry, err := s.SSO_GetUser(userid)
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- subdomain, err := utils.PostPara(r, "subdomain")
|
|
|
+ //Get the auth URL
|
|
|
+ authURL, err := utils.PostPara(r, "auth_url")
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid subdomain given")
|
|
|
+ utils.SendErrorResponse(w, "invalid auth URL given")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- delete(UserEntry.Subdomains, subdomain)
|
|
|
+ s.Config.AuthURL = authURL
|
|
|
|
|
|
- err = UserEntry.Update()
|
|
|
+ //Write to the database
|
|
|
+ err = s.Config.Database.Write("sso_conf", "authurl", authURL)
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to update user entry")
|
|
|
+ utils.SendErrorResponse(w, "failed to update auth URL")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- utils.SendOK(w)
|
|
|
-}
|
|
|
-
|
|
|
-func (s *SSOHandler) HandleEnable2FA(w http.ResponseWriter, r *http.Request) {
|
|
|
- userid, err := utils.PostPara(r, "user_id")
|
|
|
+ //Clear the cookie store and restart the server
|
|
|
+ err = s.RestartSSOServer()
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
+ utils.SendErrorResponse(w, "failed to restart SSO server")
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- if !(s.SSO_UserExists(userid)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- UserEntry, err := s.SSO_GetUser(userid)
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- UserEntry.Enable2FA = true
|
|
|
- provisionUri, err := UserEntry.ResetTotp(UserEntry.UserID, "Zoraxy-SSO")
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to reset TOTP")
|
|
|
- return
|
|
|
- }
|
|
|
- //As the ResetTotp function will update the user entry in the database, no need to call Update here
|
|
|
-
|
|
|
- js, _ := json.Marshal(provisionUri)
|
|
|
- utils.SendJSONResponse(w, string(js))
|
|
|
+ utils.SendOK(w)
|
|
|
}
|
|
|
|
|
|
-// Handle Disable 2FA for a user
|
|
|
-func (s *SSOHandler) HandleDisable2FA(w http.ResponseWriter, r *http.Request) {
|
|
|
- userid, err := utils.PostPara(r, "user_id")
|
|
|
+// HandleRegisterApp handle the request to register a new app to the SSO portal
|
|
|
+func (s *SSOHandler) HandleRegisterApp(w http.ResponseWriter, r *http.Request) {
|
|
|
+ appName, err := utils.PostPara(r, "app_name")
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "invalid user ID given")
|
|
|
+ utils.SendErrorResponse(w, "invalid app name given")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if !(s.SSO_UserExists(userid)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- UserEntry, err := s.SSO_GetUser(userid)
|
|
|
+ id, err := utils.PostPara(r, "app_id")
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
- return
|
|
|
+ //If id is not given, use the app name with a random UUID
|
|
|
+ newID, err := uuid.NewV4()
|
|
|
+ if err != nil {
|
|
|
+ utils.SendErrorResponse(w, "failed to generate new app ID")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ id = strings.ReplaceAll(appName, " ", "") + "-" + newID.String()
|
|
|
}
|
|
|
|
|
|
- UserEntry.Enable2FA = false
|
|
|
- UserEntry.TOTPCode = ""
|
|
|
-
|
|
|
- err = UserEntry.Update()
|
|
|
+ appDomain, err := utils.PostPara(r, "app_domain")
|
|
|
if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to update user entry")
|
|
|
+ utils.SendErrorResponse(w, "invalid app URL given")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- utils.SendOK(w)
|
|
|
-}
|
|
|
-
|
|
|
-// HandleVerify2FA verify the 2FA code for a user
|
|
|
-func (s *SSOHandler) HandleVerify2FA(w http.ResponseWriter, r *http.Request) (bool, error) {
|
|
|
- userid, err := utils.PostPara(r, "user_id")
|
|
|
- if err != nil {
|
|
|
- return false, errors.New("invalid user ID given")
|
|
|
+ appURLs := strings.Split(appDomain, ",")
|
|
|
+ //Remove padding and trailing spaces in each URL
|
|
|
+ for i := range appURLs {
|
|
|
+ appURLs[i] = strings.TrimSpace(appURLs[i])
|
|
|
}
|
|
|
|
|
|
- if !(s.SSO_UserExists(userid)) {
|
|
|
- utils.SendErrorResponse(w, "user not found")
|
|
|
- return false, errors.New("user not found")
|
|
|
+ //Create a new app entry
|
|
|
+ thisAppEntry := RegisteredUpstreamApp{
|
|
|
+ ID: id,
|
|
|
+ Secret: "",
|
|
|
+ Domain: appURLs,
|
|
|
+ Scopes: []string{},
|
|
|
+ SessionDuration: 3600,
|
|
|
}
|
|
|
|
|
|
- UserEntry, err := s.SSO_GetUser(userid)
|
|
|
- if err != nil {
|
|
|
- utils.SendErrorResponse(w, "failed to load user entry")
|
|
|
- return false, errors.New("failed to load user entry")
|
|
|
- }
|
|
|
-
|
|
|
- totpCode, _ := utils.PostPara(r, "totp_code")
|
|
|
-
|
|
|
- if !UserEntry.Enable2FA {
|
|
|
- //If 2FA is not enabled, return true
|
|
|
- return true, nil
|
|
|
- }
|
|
|
-
|
|
|
- if !UserEntry.VerifyTotp(totpCode) {
|
|
|
- return false, nil
|
|
|
- }
|
|
|
-
|
|
|
- return true, nil
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-/* Handlers for SSO portal server */
|
|
|
+ js, _ := json.Marshal(thisAppEntry)
|
|
|
|
|
|
-func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
|
|
|
- err := s.StartSSOPortal()
|
|
|
- if err != nil {
|
|
|
- s.Log("Failed to start SSO portal server", err)
|
|
|
- utils.SendErrorResponse(w, "failed to start SSO portal server")
|
|
|
- return
|
|
|
- }
|
|
|
- utils.SendOK(w)
|
|
|
-}
|
|
|
-
|
|
|
-func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
|
|
|
- err := s.ssoPortalServer.Close()
|
|
|
+ //Create a new app in the database
|
|
|
+ err = s.Config.Database.Write("sso_apps", appName, string(js))
|
|
|
if err != nil {
|
|
|
- s.Log("Failed to stop SSO portal server", err)
|
|
|
- utils.SendErrorResponse(w, "failed to stop SSO portal server")
|
|
|
+ utils.SendErrorResponse(w, "failed to create new app")
|
|
|
return
|
|
|
}
|
|
|
utils.SendOK(w)
|