2023-09-06 22:20:09 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2023-09-09 01:38:10 +01:00
|
|
|
"github.com/1f349/tulip/database"
|
|
|
|
"github.com/1f349/tulip/pages"
|
|
|
|
"github.com/1f349/tulip/scope"
|
2023-09-06 22:20:09 +01:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
)
|
|
|
|
|
2023-09-09 01:38:10 +01:00
|
|
|
func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
2023-09-06 22:20:09 +01:00
|
|
|
// function is only called with GET or POST method
|
|
|
|
isPost := req.Method == http.MethodPost
|
|
|
|
|
|
|
|
var form url.Values
|
|
|
|
if isPost {
|
2023-09-09 01:38:10 +01:00
|
|
|
err := req.ParseForm()
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "Failed to parse form", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
form = req.PostForm
|
|
|
|
} else {
|
|
|
|
form = req.URL.Query()
|
|
|
|
}
|
|
|
|
|
|
|
|
clientID := form.Get("client_id")
|
|
|
|
client, err := h.oauthMgr.GetClient(req.Context(), clientID)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "Invalid client", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
redirectUri := form.Get("redirect_uri")
|
|
|
|
if redirectUri != client.GetDomain() {
|
|
|
|
http.Error(rw, "Incorrect redirect URI", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if form.Has("cancel") {
|
|
|
|
uCancel, err := url.Parse(client.GetDomain())
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "Invalid redirect URI", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
q := uCancel.Query()
|
|
|
|
q.Set("error", "access_denied")
|
|
|
|
uCancel.RawQuery = q.Encode()
|
|
|
|
|
|
|
|
http.Redirect(rw, req, uCancel.String(), http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var isSSO bool
|
|
|
|
if clientIsSSO, ok := client.(interface{ IsSSO() bool }); ok {
|
|
|
|
isSSO = clientIsSSO.IsSSO()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case isSSO && isPost:
|
2023-09-09 01:38:10 +01:00
|
|
|
http.Error(rw, "400 Bad Request: Not sure how you even managed to send a POST request for an SSO application", http.StatusBadRequest)
|
2023-09-06 22:20:09 +01:00
|
|
|
return
|
|
|
|
case !isSSO && !isPost:
|
2023-09-09 01:38:10 +01:00
|
|
|
// find application redirect domain and name
|
|
|
|
appUrlFull, err := url.Parse(client.GetDomain())
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "500 Internal Server Error: Failed to parse application redirect URL", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
appDomain := appUrlFull.Scheme + "://" + appUrlFull.Host
|
|
|
|
appName := appUrlFull.Host
|
|
|
|
if clientGetName, ok := client.(interface{ GetName() string }); ok {
|
|
|
|
n := clientGetName.GetName()
|
|
|
|
if n != "" {
|
|
|
|
appName = n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var user *database.User
|
2023-09-15 13:06:31 +01:00
|
|
|
var hasOtp bool
|
|
|
|
if h.DbTx(rw, func(tx *database.Tx) (err error) {
|
2023-09-09 01:38:10 +01:00
|
|
|
user, err = tx.GetUserDisplayName(auth.Data.ID)
|
2023-09-15 13:06:31 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hasOtp, err = tx.HasTwoFactor(auth.Data.ID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
2023-09-09 01:38:10 +01:00
|
|
|
}) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-15 13:49:58 +01:00
|
|
|
scopeList := form.Get("scope")
|
|
|
|
if !scope.ScopesExist(scopeList) {
|
|
|
|
http.Error(rw, "Invalid scopes", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-06 22:20:09 +01:00
|
|
|
rw.WriteHeader(http.StatusOK)
|
2023-09-09 01:38:10 +01:00
|
|
|
pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{
|
2023-10-10 18:06:43 +01:00
|
|
|
"ServiceName": h.conf.ServiceName,
|
2023-09-09 01:38:10 +01:00
|
|
|
"AppName": appName,
|
|
|
|
"AppDomain": appDomain,
|
|
|
|
"User": user,
|
2023-10-15 13:49:58 +01:00
|
|
|
"WantsList": scope.FancyScopeList(scopeList),
|
2023-09-09 01:38:10 +01:00
|
|
|
"ResponseType": form.Get("response_type"),
|
|
|
|
"ResponseMode": form.Get("response_mode"),
|
|
|
|
"ClientID": form.Get("client_id"),
|
|
|
|
"RedirectUri": form.Get("redirect_uri"),
|
|
|
|
"State": form.Get("state"),
|
2023-10-15 13:49:58 +01:00
|
|
|
"Scope": scopeList,
|
2023-09-09 01:38:10 +01:00
|
|
|
"Nonce": form.Get("nonce"),
|
2023-09-15 13:06:31 +01:00
|
|
|
"HasOtp": hasOtp,
|
2023-09-09 01:38:10 +01:00
|
|
|
})
|
2023-09-06 22:20:09 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-15 13:06:31 +01:00
|
|
|
if !isSSO {
|
|
|
|
otpInput := req.FormValue("code")
|
|
|
|
if h.fetchAndValidateOtp(rw, auth.Data.ID, otpInput) {
|
2023-09-06 22:20:09 +01:00
|
|
|
return
|
|
|
|
}
|
2023-09-15 13:06:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// redirect with an error if the action is not authorize
|
|
|
|
if form.Get("oauth_action") == "authorize" || isSSO {
|
|
|
|
if err := h.oauthSrv.HandleAuthorizeRequest(rw, req); err != nil {
|
|
|
|
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
|
|
}
|
2023-09-09 01:38:10 +01:00
|
|
|
return
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
2023-09-15 13:06:31 +01:00
|
|
|
parsedRedirect, err := url.Parse(redirectUri)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "400 Bad Request: Invalid redirect URI", http.StatusBadRequest)
|
|
|
|
return
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
2023-09-15 13:06:31 +01:00
|
|
|
q := parsedRedirect.Query()
|
|
|
|
q.Set("error", "user_cancelled")
|
|
|
|
parsedRedirect.RawQuery = q.Encode()
|
|
|
|
http.Redirect(rw, req, parsedRedirect.String(), http.StatusFound)
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Request) (string, error) {
|
|
|
|
err := req.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2023-09-15 13:06:31 +01:00
|
|
|
auth, err := internalAuthenticationHandler(rw, req)
|
2023-09-06 22:20:09 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if auth.IsGuest() {
|
|
|
|
// handle redirecting to oauth
|
|
|
|
var q url.Values
|
|
|
|
switch req.Method {
|
|
|
|
case http.MethodPost:
|
|
|
|
q = req.PostForm
|
|
|
|
case http.MethodGet:
|
|
|
|
q = req.URL.Query()
|
|
|
|
default:
|
|
|
|
http.Error(rw, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
|
|
|
return "", err
|
|
|
|
}
|
2023-09-09 01:38:10 +01:00
|
|
|
|
|
|
|
redirectUrl := PrepareRedirectUrl("/login", &url.URL{Path: "/authorize", RawQuery: q.Encode()})
|
|
|
|
http.Redirect(rw, req, redirectUrl.String(), http.StatusFound)
|
2023-09-06 22:20:09 +01:00
|
|
|
return "", nil
|
|
|
|
}
|
2023-09-09 01:38:10 +01:00
|
|
|
return auth.Data.ID.String(), nil
|
2023-09-06 22:20:09 +01:00
|
|
|
}
|