123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- package sso
- import (
- "embed"
- "net/http"
- "github.com/gorilla/sessions"
- "imuslab.com/zoraxy/mod/database"
- "imuslab.com/zoraxy/mod/info/logger"
- )
- /*
- sso.go
- This file contains the main SSO handler and the SSO configuration
- structure. It also contains the main SSO handler functions.
- SSO web interface are stored in the static folder, which is embedded
- into the binary.
- */
- //go:embed static/*
- var staticFiles embed.FS //Static files for the SSO portal
- type SSOConfig struct {
- SystemUUID string //System UUID, should be passed in from main scope
- AuthURL string //Authentication subdomain URL, e.g. auth.example.com
- PortalServerPort int //SSO portal server port
- Database *database.Database //System master key-value database
- Logger *logger.Logger
- }
- // SSOHandler is the main SSO handler structure
- type SSOHandler struct {
- cookieStore *sessions.CookieStore
- ssoPortalServer *http.Server
- ssoPortalMux *http.ServeMux
- Oauth2Server *OAuth2Server
- Config *SSOConfig
- Apps map[string]RegisteredUpstreamApp
- }
- // Create a new Zoraxy SSO handler
- func NewSSOHandler(config *SSOConfig) (*SSOHandler, error) {
- //Create a cookie store for the SSO handler
- cookieStore := sessions.NewCookieStore([]byte(config.SystemUUID))
- cookieStore.Options = &sessions.Options{
- Path: "",
- Domain: "",
- MaxAge: 0,
- Secure: false,
- HttpOnly: false,
- SameSite: 0,
- }
- config.Database.NewTable("sso_users") //For storing user information
- config.Database.NewTable("sso_conf") //For storing SSO configuration
- config.Database.NewTable("sso_apps") //For storing registered apps
- //Create the SSO Handler
- thisHandler := SSOHandler{
- cookieStore: cookieStore,
- Config: config,
- }
- //Read the app info from database
- thisHandler.Apps = make(map[string]RegisteredUpstreamApp)
- //Create an oauth2 server
- oauth2Server, err := NewOAuth2Server(config, &thisHandler)
- if err != nil {
- return nil, err
- }
- //Register endpoints
- thisHandler.Oauth2Server = oauth2Server
- thisHandler.InitSSOPortal(config.PortalServerPort)
- return &thisHandler, nil
- }
- func (h *SSOHandler) RestorePreviousRunningState() {
- //Load the previous SSO state
- ssoEnabled := false
- ssoPort := 5488
- ssoAuthURL := ""
- h.Config.Database.Read("sso_conf", "enabled", &ssoEnabled)
- h.Config.Database.Read("sso_conf", "port", &ssoPort)
- h.Config.Database.Read("sso_conf", "authurl", &ssoAuthURL)
- if ssoAuthURL == "" {
- //Cannot enable SSO without auth URL
- ssoEnabled = false
- }
- h.Config.PortalServerPort = ssoPort
- h.Config.AuthURL = ssoAuthURL
- if ssoEnabled {
- go h.StartSSOPortal()
- }
- }
- // ServeForwardAuth handle the SSO request in interception mode
- // Suppose to be called in dynamicproxy.
- // Return true if the request is allowed to pass, false if the request is blocked and shall not be further processed
- func (h *SSOHandler) ServeForwardAuth(w http.ResponseWriter, r *http.Request) bool {
- //Get the current uri for appending to the auth subdomain
- originalRequestURL := r.RequestURI
- redirectAuthURL := h.Config.AuthURL
- if redirectAuthURL == "" || !h.IsRunning() {
- //Redirect not set or auth server is offlined
- w.Write([]byte("SSO auth URL not set or SSO server offline."))
- //TODO: Use better looking template if exists
- return false
- }
- //Check if the user have the cookie "Zoraxy-SSO" set
- session, err := h.cookieStore.Get(r, "Zoraxy-SSO")
- if err != nil {
- //Redirect to auth subdomain
- http.Redirect(w, r, redirectAuthURL+"/sso/login?m=new&t="+originalRequestURL, http.StatusFound)
- return false
- }
- //Check if the user is logged in
- if session.Values["username"] != true {
- //Redirect to auth subdomain
- http.Redirect(w, r, redirectAuthURL+"/sso/login?m=expired&t="+originalRequestURL, http.StatusFound)
- return false
- }
- //Check if the current request subdomain is allowed
- userName := session.Values["username"].(string)
- user, err := h.GetSSOUser(userName)
- if err != nil {
- //User might have been removed from SSO. Redirect to auth subdomain
- http.Redirect(w, r, redirectAuthURL, http.StatusFound)
- return false
- }
- //Check if the user have access to the current subdomain
- if !user.Subdomains[r.Host].AllowAccess {
- //User is not allowed to access the current subdomain. Sent 403
- http.Error(w, "Forbidden", http.StatusForbidden)
- //TODO: Use better looking template if exists
- return false
- }
- //User is logged in, continue to the next handler
- return true
- }
- // Log a message with the SSO module tag
- func (h *SSOHandler) Log(message string, err error) {
- h.Config.Logger.PrintAndLog("SSO", message, err)
- }
|