Added new test-build that includes OpenID connect to test with KeyCloak

This commit is contained in:
frikky
2022-02-02 04:15:16 +01:00
parent 4e3f0833ad
commit c11051c7eb
4 changed files with 308 additions and 85 deletions
+2 -1
View File
@@ -22,8 +22,9 @@ require (
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.4.2
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.1.86 github.com/shuffle/shuffle-shared v0.1.94
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
google.golang.org/api v0.65.0 google.golang.org/api v0.65.0
+71 -10
View File
@@ -11,6 +11,7 @@ import (
//"crypto/tls" //"crypto/tls"
//"crypto/x509" //"crypto/x509"
"encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -18,6 +19,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -53,6 +55,8 @@ import (
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory" "github.com/go-git/go-git/v5/storage/memory"
//cv "github.com/nirasan/go-oauth-pkce-code-verifier"
//githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" //githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
// Random // Random
@@ -1166,8 +1170,16 @@ func handleContact(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Thanks for reaching out. We will contact you soon!"}`))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Thanks for reaching out. We will contact you soon!"}`)))
} }
func verifier() (*shuffle.CodeVerifier, error) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, 32, 32)
for i := 0; i < 32; i++ {
b[i] = byte(r.Intn(255))
}
return shuffle.CreateCodeVerifierFromBytes(b)
}
func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
log.Printf("In admin login request?")
cors := shuffle.HandleCors(resp, request) cors := shuffle.HandleCors(resp, request)
if cors { if cors {
return return
@@ -1190,10 +1202,63 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
return return
} }
//ssoUrl = org.SSOConfig.SOSOEntrypoint baseSSOUrl := ""
redirectUri := shuffle.SSOUrl handled := []string{}
for _, user := range users {
if shuffle.ArrayContains(handled, user.ActiveOrg.Id) {
continue
}
handled = append(handled, user.ActiveOrg.Id)
org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
if err != nil {
log.Printf("[WARNING] Error getting org in admin check: %s", err)
continue
}
// No childorg setup, only parent org
if len(org.ManagerOrgs) > 0 || len(org.CreatorOrg) > 0 {
continue
}
// Should run calculations
if len(org.SSOConfig.OpenIdAuthorization) > 0 {
log.Printf("[DEBUG] Found OpenID url (PKCE). Extra redirect check: %s", request.URL.String())
baseSSOUrl = org.SSOConfig.OpenIdAuthorization
codeChallenge := uuid.NewV4().String()
//h.Write([]byte(v.Value))
verifier, verifiererr := verifier()
if verifiererr == nil {
codeChallenge = verifier.Value
}
//log.Printf("[DEBUG] Got challenge value %s (pre state)", codeChallenge)
redirectUrl := url.QueryEscape("http://localhost:5001/api/v1/login_openid")
state := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("org=%s&challenge=%s&redirect=%s", org.Id, codeChallenge, redirectUrl)))
// has to happen after initial value is stored
if verifiererr == nil {
codeChallenge = verifier.CodeChallengeS256()
}
//log.Printf("[DEBUG] Got challenge value %s (POST state)", codeChallenge)
baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&code_challenge_method=S256&code_challenge=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, codeChallenge)
break
}
if len(org.SSOConfig.SSOEntrypoint) > 0 {
log.Printf("[DEBUG] Found SAML SSO url")
baseSSOUrl = org.SSOConfig.SSOEntrypoint
break
}
}
log.Printf("[DEBUG] URL: %s", baseSSOUrl)
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, redirectUri))) resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, baseSSOUrl)))
} }
func handleLogin(resp http.ResponseWriter, request *http.Request) { func handleLogin(resp http.ResponseWriter, request *http.Request) {
@@ -3325,11 +3390,6 @@ func createFs(basepath, pathname string) (billy.Filesystem, error) {
return err return err
} }
//if strings.Contains(path, "yaml") {
// log.Printf("PATH: %s -> %s", path, fullpath)
// //log.Printf("DATA: %s", string(srcData))
//}
dst, err := fs.Create(fullpath) dst, err := fs.Create(fullpath)
if err != nil { if err != nil {
log.Printf("Dst error: %s", err) log.Printf("Dst error: %s", err)
@@ -5882,7 +5942,8 @@ func initHandlers() {
// Docker orborus specific - downloads an image // Docker orborus specific - downloads an image
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "OPTIONS")
// Important for email, IDS etc. Create this by: // Important for email, IDS etc. Create this by:
// PS: For cloud, this has to use cloud storage. // PS: For cloud, this has to use cloud storage.
+222 -73
View File
@@ -97,6 +97,30 @@ const OrgHeader = (props) => {
? "" ? ""
: selectedOrganization.defaults.notification_workflow : selectedOrganization.defaults.notification_workflow
); );
const [openidClientId, setOpenidClientId] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.client_id === undefined ||
selectedOrganization.sso_config.client_id.length === 0
? ""
: selectedOrganization.sso_config.client_id
);
const [openidAuthorization, setOpenidAuthorization] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.openid_authorization === undefined ||
selectedOrganization.sso_config.openid_authorization.length === 0
? ""
: selectedOrganization.sso_config.openid_authorization
);
const [openidToken, setOpenidToken] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.openid_token=== undefined ||
selectedOrganization.sso_config.openid_token.length === 0
? ""
: selectedOrganization.sso_config.openidtoken_
)
const [file, setFile] = React.useState(""); const [file, setFile] = React.useState("");
const [fileBase64, setFileBase64] = React.useState( const [fileBase64, setFileBase64] = React.useState(
@@ -145,6 +169,7 @@ const OrgHeader = (props) => {
defaults, defaults,
sso_config sso_config
) => { ) => {
const data = { const data = {
name: name, name: name,
description: description, description: description,
@@ -216,6 +241,9 @@ const OrgHeader = (props) => {
{ {
sso_entrypoint: ssoEntrypoint, sso_entrypoint: ssoEntrypoint,
sso_certificate: ssoCertificate, sso_certificate: ssoCertificate,
client_id: openidClientId,
openid_authorization: openidAuthorization,
openid_token: openidToken,
} }
) )
} }
@@ -548,79 +576,200 @@ const OrgHeader = (props) => {
</span> </span>
</Grid> </Grid>
)} )}
<Grid item xs={6} style={{}}> {isCloud ? null :
<span> <Grid item xs={12} style={{marginTop: 50 }}>
<Typography>SSO Entrypoint (IdP)</Typography> <Typography variant="h4" style={{textAlign: "center",}}>OpenID connect</Typography>
<TextField <Grid container style={{marginTop: 10, }}>
required <Grid item xs={4} style={{}}>
style={{ <span>
flex: "1", <Typography>Client ID</Typography>
marginTop: "5px", <TextField
marginRight: "15px", required
backgroundColor: theme.palette.inputColor, style={{
}} flex: "1",
fullWidth={true} marginTop: "5px",
type="name" marginRight: "15px",
multiline={true} backgroundColor: theme.palette.inputColor,
rows={2} }}
disabled={ fullWidth={true}
selectedOrganization.manager_orgs !== undefined && type="name"
selectedOrganization.manager_orgs !== null && multiline={true}
selectedOrganization.manager_orgs.length > 0 rows={2}
} disabled={
id="outlined-with-placeholder" selectedOrganization.manager_orgs !== undefined &&
margin="normal" selectedOrganization.manager_orgs !== null &&
variant="outlined" selectedOrganization.manager_orgs.length > 0
placeholder="The entrypoint URL from your provider" }
value={ssoEntrypoint} id="outlined-with-placeholder"
onChange={(e) => { margin="normal"
setSsoEntrypoint(e.target.value); variant="outlined"
}} placeholder="The OpenID client ID from the identity provider"
InputProps={{ value={openidClientId}
classes: { onChange={(e) => {
notchedOutline: classes.notchedOutline, setOpenidClientId(e.target.value);
}, }}
style: { InputProps={{
color: "white", classes: {
}, notchedOutline: classes.notchedOutline,
}} },
/> style: {
</span> color: "white",
</Grid> },
<Grid item xs={6} style={{}}> }}
<span> />
<Typography>SSO Certificate (X509)</Typography> </span>
<TextField </Grid>
required <Grid item xs={4} style={{}}>
style={{ <span>
flex: "1", <Typography>Authorization URL</Typography>
marginTop: "5px", <TextField
marginRight: "15px", required
backgroundColor: theme.palette.inputColor, style={{
}} flex: "1",
fullWidth={true} marginTop: "5px",
type="name" marginRight: "15px",
id="outlined-with-placeholder" backgroundColor: theme.palette.inputColor,
margin="normal" }}
variant="outlined" fullWidth={true}
multiline={true} type="name"
rows={2} id="outlined-with-placeholder"
placeholder="The X509 certificate to use" margin="normal"
value={ssoCertificate} variant="outlined"
onChange={(e) => { multiline={true}
setSsoCertificate(e.target.value); rows={2}
}} placeholder="The OpenID authorization URL (usually ends with /authorize)"
InputProps={{ value={openidAuthorization}
classes: { onChange={(e) => {
notchedOutline: classes.notchedOutline, setOpenidAuthorization(e.target.value)
}, }}
style: { InputProps={{
color: "white", classes: {
}, notchedOutline: classes.notchedOutline,
}} },
/> style: {
</span> color: "white",
</Grid> },
}}
/>
</span>
</Grid>
<Grid item xs={4} style={{}}>
<span>
<Typography>Token URL</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The OpenID token URL (usually ends with /token)"
value={openidToken}
onChange={(e) => {
setOpenidToken(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
}
{isCloud ? null :
<Grid item xs={12} style={{marginTop: 50,}}>
<Typography variant="h4" style={{textAlign: "center",}}>SAML SSO (v1.1)</Typography>
<Grid container style={{marginTop: 10, }}>
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Entrypoint (IdP)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="The entrypoint URL from your provider"
value={ssoEntrypoint}
onChange={(e) => {
setSsoEntrypoint(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Certificate (X509)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The X509 certificate to use"
value={ssoCertificate}
onChange={(e) => {
setSsoCertificate(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
}
{/* {/*
<span style={{textAlign: "center"}}> <span style={{textAlign: "center"}}>
{expanded ? {expanded ?
+13 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable react/no-multi-comp */ /* eslint-disable react/no-multi-comp */
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
@@ -55,6 +55,7 @@ const LoginDialog = (props) => {
const [MFAField, setMFAField] = useState(false); const [MFAField, setMFAField] = useState(false);
const [MFAValue, setMFAValue] = useState(""); const [MFAValue, setMFAValue] = useState("");
// Used to swap from login to register. True = login, false = register // Used to swap from login to register. True = login, false = register
const classes = useStyles(); const classes = useStyles();
@@ -468,6 +469,7 @@ const LoginDialog = (props) => {
<div style={{ textAlign: "center", margin: 10 }}> <div style={{ textAlign: "center", margin: 10 }}>
<Button <Button
fullWidth fullWidth
id="sso_button"
color="secondary" color="secondary"
variant="outlined" variant="outlined"
type="button" type="button"
@@ -491,6 +493,16 @@ const LoginDialog = (props) => {
const loadedCheck = isLoaded ? <div>{basedata}</div> : <div></div>; const loadedCheck = isLoaded ? <div>{basedata}</div> : <div></div>;
useEffect(() => {
if (ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0) {
//id="sso_button"
const ssoBtn = document.getElementById("sso_button");
if (ssoBtn !== undefined && ssoBtn !== null) {
console.log("SSO BTN: ", ssoBtn)
}
}
}, [ssoUrl])
return <div>{loadedCheck}</div>; return <div>{loadedCheck}</div>;
}; };