Added a default organization with migration

This commit is contained in:
frikky
2020-10-10 11:42:33 +02:00
parent afc0117423
commit acf2a2e919
5 changed files with 371 additions and 59 deletions
+284 -37
View File
@@ -181,6 +181,7 @@ type User struct {
Id string `datastore:"id" json:"id"`
Orgs []string `datastore:"orgs" json:"orgs"`
CreationTime int64 `datastore:"creation_time" json:"creation_time"`
ActiveOrg Org `json:"active_org" datastore:"active_org"`
Active bool `datastore:"active" json:"active"`
}
@@ -1013,13 +1014,13 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
user, err := handleApiAuthentication(resp, request)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`))
resp.Write([]byte(`{"success": false, "reason": "Can't handle set env auth"}`))
return
}
if user.Role != "admin" {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`))
resp.Write([]byte(`{"success": false, "reason": "Can't set environment without being admin"}`))
return
}
@@ -1097,7 +1098,7 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(`{"success": true}`))
}
func createNewUser(username, password, role, apikey string) error {
func createNewUser(username, password, role, apikey string, org Org) error {
// Returns false if there is an issue
// Use this for register
err := checkPasswordStrength(password)
@@ -1137,7 +1138,7 @@ func createNewUser(username, password, role, apikey string) error {
newUser.Verified = false
newUser.CreationTime = time.Now().Unix()
newUser.Active = true
newUser.Orgs = []string{"default"}
newUser.Orgs = []string{org.Id}
// FIXME - Remove this later
if role == "admin" {
@@ -1148,6 +1149,8 @@ func createNewUser(username, password, role, apikey string) error {
newUser.Roles = []string{"user"}
}
newUser.ActiveOrg = org
if len(apikey) > 0 {
newUser.ApiKey = apikey
}
@@ -1182,25 +1185,25 @@ func createNewUser(username, password, role, apikey string) error {
log.Printf("Error adding User %s: %s", username, err)
return err
}
url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
const verifyMessage = `
Registration URL :)
%s
`
addr := newUser.Username
msg := &mail.Message{
Sender: "Shuffle <frikky@shuffler.io>",
To: []string{addr},
Subject: "Verify your username - Shuffle",
Body: fmt.Sprintf(verifyMessage, url),
}
log.Println(msg.Body)
if err := mail.Send(ctx, msg); err != nil {
log.Printf("Couldn't send email: %v", err)
}
// url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String())
// const verifyMessage = `
//Registration URL :)
//
//%s
// `
// addr := newUser.Username
//
// msg := &mail.Message{
// Sender: "Shuffle <frikky@shuffler.io>",
// To: []string{addr},
// Subject: "Verify your username - Shuffle",
// Body: fmt.Sprintf(verifyMessage, url),
// }
//
// log.Println(msg.Body)
// if err := mail.Send(ctx, msg); err != nil {
// log.Printf("Couldn't send email: %v", err)
// }
err = increaseStatisticsField(ctx, "successful_register", username, 1)
if err != nil {
@@ -1249,7 +1252,8 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
if count == 0 {
role = "admin"
}
err = createNewUser(data.Username, data.Password, role, "")
err = createNewUser(data.Username, data.Password, role, "", user.ActiveOrg)
if err != nil {
log.Printf("Failed registering user: %s", err)
resp.WriteHeader(401)
@@ -1623,6 +1627,10 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
// This is a long check to see if an inactive admin can access the site
parsedAdmin := "false"
if userInfo.Role == "admin" {
parsedAdmin = "true"
}
if !userInfo.Active {
if userInfo.Role == "admin" {
parsedAdmin = "true"
@@ -1692,14 +1700,55 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
Expires: expiration,
})
// Migrate this to real Org
// Need to create org endpoints (create, delete etc)
currentOrg := `{
"name": "Shuffle",
"id": "123",
"role": "admin",
"cloud_sync": false
}`
// Updating user info if there's something wrong
if (len(userInfo.ActiveOrg.Name) == 0 || len(userInfo.ActiveOrg.Id) == 0) && len(userInfo.Orgs) > 0 {
_, err := getOrg(ctx, userInfo.Orgs[0])
if err != nil {
var orgs []Org
q := datastore.NewQuery("Organizations")
_, err = dbclient.GetAll(ctx, q, &orgs)
if err == nil {
newStringOrgs := []string{}
newOrgs := []Org{}
for _, org := range orgs {
if strings.ToLower(org.Name) == strings.ToLower(userInfo.Orgs[0]) {
newOrgs = append(newOrgs, org)
newStringOrgs = append(newStringOrgs, org.Id)
}
}
if len(newOrgs) > 0 {
userInfo.ActiveOrg = newOrgs[0]
userInfo.Orgs = newStringOrgs
err = setUser(ctx, &userInfo)
if err != nil {
log.Printf("Error patching User for activeOrg: %s", err)
} else {
log.Printf("Updated the users' org")
}
}
} else {
log.Printf("Failed getting orgs for user. Major issue.: %s", err)
}
} else {
// 1. Check if the org exists by ID
// 2. if it does, overwrite user
userInfo.ActiveOrg = Org{
Id: userInfo.Orgs[0],
}
err = setUser(ctx, &userInfo)
if err != nil {
log.Printf("Error patching User for activeOrg: %s", err)
}
}
}
currentOrg, err := json.Marshal(userInfo.ActiveOrg)
if err != nil {
currentOrg = []byte("{}")
}
returnData := fmt.Sprintf(`
{
@@ -2175,6 +2224,61 @@ func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) {
resp.Write(newjson)
}
func handleGetOrgs(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in set new workflowhandler: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Role != "admin" {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Not admin"}`))
return
}
ctx := context.Background()
var orgs []Org
q := datastore.NewQuery("Organizations")
_, err = dbclient.GetAll(ctx, q, &orgs)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Can't get users"}`))
return
}
//newUsers := []User{}
//for _, item := range users {
// if len(item.Username) == 0 {
// continue
// }
// item.Password = ""
// item.Session = ""
// item.VerificationToken = ""
// newUsers = append(newUsers, item)
//}
newjson, err := json.Marshal(orgs)
if err != nil {
log.Printf("Failed unmarshal: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`)))
return
}
resp.WriteHeader(200)
resp.Write(newjson)
}
func handleGetUsers(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
@@ -2392,6 +2496,28 @@ func getSession(ctx context.Context, thissession string) (*session, error) {
return curUser, nil
}
// ListBooks returns a list of books, ordered by title.
func getOrg(ctx context.Context, id string) (*Org, error) {
key := datastore.NameKey("Organizations", id, nil)
curOrg := &Org{}
if err := dbclient.Get(ctx, key, curOrg); err != nil {
return &Org{}, err
}
return curOrg, nil
}
func setOrg(ctx context.Context, data Org, id string) error {
// clear session_token and API_token for user
k := datastore.NameKey("Organizations", id, nil)
if _, err := dbclient.Put(ctx, k, &data); err != nil {
log.Println(err)
return err
}
return nil
}
// ListBooks returns a list of books, ordered by title.
func getUser(ctx context.Context, id string) (*User, error) {
key := datastore.NameKey("Users", id, nil)
@@ -4791,8 +4917,8 @@ func setTriggerAuth(ctx context.Context, trigger TriggerAuth) error {
func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
conf := &oauth2.Config{
ClientID: "70e37005-c954-4290-b573-d4b94e484336",
ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392",
ClientID: "",
ClientSecret: "",
Scopes: []string{
"Mail.Read",
"User.Read",
@@ -6243,6 +6369,91 @@ func runInit(ctx context.Context) {
}
*/
setUsers := false
orgQuery := datastore.NewQuery("Organizations")
var activeOrgs []Org
_, err = dbclient.GetAll(ctx, orgQuery, &activeOrgs)
if err != nil {
log.Printf("Error getting organizations!")
} else {
// Add all users to it
if len(activeOrgs) == 1 {
setUsers = true
}
log.Printf("Organizations exist!")
if len(activeOrgs) == 0 {
log.Printf(`No orgs. Setting org "default"`)
orgSetupName := "default"
orgId := uuid.NewV4().String()
newOrg := Org{
Name: orgSetupName,
Id: orgId,
Org: orgSetupName,
Users: []User{},
Roles: []string{"admin", "user"},
CloudSync: false,
}
err = setOrg(ctx, newOrg, orgId)
if err != nil {
log.Printf("Failed setting organization: %s", err)
} else {
log.Printf("Successfully created the default org!")
setUsers = true
}
} else {
log.Printf("There are %d org(s).", len(activeOrgs))
}
}
// Adding the users to the base organization since only one exists (default)
if setUsers && len(activeOrgs) > 0 {
activeOrg := activeOrgs[0]
q := datastore.NewQuery("Users")
var users []User
_, err = dbclient.GetAll(ctx, q, &users)
if err == nil {
setOrgBool := false
for _, user := range users {
newUser := User{
Username: user.Username,
Id: user.Id,
ActiveOrg: Org{
Id: activeOrg.Id,
},
Orgs: []string{activeOrg.Id},
Role: user.Role,
}
found := false
for _, orgUser := range activeOrg.Users {
if user.Id == orgUser.Id {
found = true
}
}
if !found && len(user.Username) > 0 {
log.Printf("Adding user %s to org %s", user.Username, activeOrg.Name)
activeOrg.Users = append(activeOrg.Users, newUser)
setOrgBool = true
}
}
if setOrgBool {
err = setOrg(ctx, activeOrg, activeOrg.Id)
if err != nil {
log.Printf("Failed setting org %s: %s!", activeOrg.Name, err)
} else {
log.Printf("UPDATED org %s!", activeOrg.Name)
}
}
}
log.Printf("Should add %d users to organization default", len(users))
}
// Fix active users etc
q := datastore.NewQuery("Users").Filter("active =", true)
var activeusers []User
@@ -6253,6 +6464,7 @@ func runInit(ctx context.Context) {
q := datastore.NewQuery("Users")
var users []User
_, err := dbclient.GetAll(ctx, q, &users)
if len(activeusers) == 0 && len(users) > 0 {
log.Printf("No active users found - setting ALL to active")
if err == nil {
@@ -6268,7 +6480,12 @@ func runInit(ctx context.Context) {
}
if len(user.Orgs) == 0 {
user.Orgs = []string{"default"}
defaultName := "default"
user.Orgs = []string{defaultName}
user.ActiveOrg = Org{
Name: defaultName,
Role: "user",
}
}
err = setUser(ctx, &user)
@@ -6291,7 +6508,11 @@ func runInit(ctx context.Context) {
log.Printf("SHUFFLE_DEFAULT_USERNAME and SHUFFLE_DEFAULT_PASSWORD not defined as environments. Running without default user.")
} else {
apikey := os.Getenv("SHUFFLE_DEFAULT_APIKEY")
err = createNewUser(username, password, "admin", apikey)
tmpOrg := Org{
Name: "default",
}
err = createNewUser(username, password, "admin", apikey, tmpOrg)
if err != nil {
log.Printf("Failed to create default user %s: %s", username, err)
} else {
@@ -6504,6 +6725,31 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
log.Printf("Apidata: %s", tmpData.Apikey)
// FIXME: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit
client := &http.Client{}
syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync"
req, err := http.NewRequest(
"POST",
syncPath,
nil,
)
newresp, err := client.Do(req)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s"`, err)))
//setBadMemcache(ctx, docPath)
return
}
respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil {
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse sync data"`)))
return
}
log.Printf("Respbody: %s", string(respBody))
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
}
@@ -6625,8 +6871,9 @@ func init() {
r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
// NEW for 0.8.0
r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS")
//r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS")
http.Handle("/", r)
}
+7 -6
View File
@@ -68,12 +68,13 @@ type ExecutionRequest struct {
// Role is just used for feedback for a user
type Org struct {
Name string `json:"name"`
Org string `json:"org"`
Users []User `json:"users"`
Id string `json:"id"`
Role string `json:"role"`
CloudSync bool `json:"cloud_sync"`
Name string `json:"name" datastore:"name"`
Id string `json:"id" datastore:"id"`
Org string `json:"org" datastore:"org"`
Users []User `json:"users" datastore:"users"`
Role string `json:"role" datastore:"role"`
Roles []string `json:"roles" datastore:"roles"`
CloudSync bool `json:"cloud_sync" datastore:"CloudSync"`
}
type AppAuthenticationStorage struct {
+13 -12
View File
@@ -101,7 +101,6 @@ const Header = props => {
// Should be based on some path
const logoCheck = !homePage ? null : null
// Handle top bar or something
const loginTextBrowser = !isLoggedIn ?
<div style={{display: "flex"}}>
@@ -184,17 +183,19 @@ const Header = props => {
color="primary"> Settings</Button>
</Link>
</ListItem>
<ListItem>
<Link to="/admin" style={hrefStyle}>
<Button
style={{}}
variant="contained"
color="primary"
>
Admin
</Button>
</Link>
</ListItem>
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
<ListItem>
<Link to="/admin" style={hrefStyle}>
<Button
style={{}}
variant="contained"
color="primary"
>
Admin
</Button>
</Link>
</ListItem>
}
{userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null :
<ListItem>
<Select
+66 -2
View File
@@ -45,6 +45,7 @@ const Admin = (props) => {
const [loginInfo, setLoginInfo] = React.useState("");
const [curTab, setCurTab] = React.useState(0);
const [users, setUsers] = React.useState([]);
const [organizations, setOrganizations] = React.useState([]);
const [environments, setEnvironments] = React.useState([]);
const [authentication, setAuthentication] = React.useState([]);
const [schedules, setSchedules] = React.useState([])
@@ -512,6 +513,31 @@ const Admin = (props) => {
});
}
const getOrgs = () => {
fetch(globalUrl + "/api/v1/getorgs", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!")
return
}
return response.json()
})
.then((responseJson) => {
setOrganizations(responseJson)
})
.catch(error => {
alert.error(error.toString())
});
}
const getUsers = () => {
fetch(globalUrl + "/api/v1/getusers", {
method: 'GET',
@@ -523,7 +549,7 @@ const Admin = (props) => {
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!")
window.location.pathname = "/workflows"
return
}
@@ -771,7 +797,7 @@ const Admin = (props) => {
Enable cloud features
</span></DialogTitle>
<DialogContent>
What does <a href="https://shuffler.io/docs/workflows#workflow_variables" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
What does <a href="https://shuffler.io/docs/hybrid#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
<div style={{marginTop: 5}}/>
Cloud Apikey
<div style={{display: "flex", marginBottom: 20, }}>
@@ -1402,6 +1428,42 @@ const Admin = (props) => {
:
null
}
{organizations !== undefined && organizations !== null && organizations.length > 0 ?
<span>
{organizations.map((data, index) => {
const isSelected = props.userdata.selected_org === undefined ? "False" : props.userdata.selected_org.id === data.id ? "True" : "False"
return (
<ListItem key={index}>
<ListItemText
primary={data.name}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.id}
style={{minWidth: 200, maxWidth: 200}}
/>
<ListItemText
primary={data.role}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={isSelected}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary=<Switch checked={data.cloud_sync} onChange={() => {
setCloudSyncModalOpen(true)
setSelectedOrganization(data)
console.log("INVERT CLOUD SYNC")
}} />
style={{minWidth: 150, maxWidth: 150}}
/>
</ListItem>
)
})}
</span>
: null}
</List>
</div>
: null
@@ -1455,6 +1517,8 @@ const Admin = (props) => {
getEnvironments()
} else if (newValue === 3) {
getSchedules()
} else if (newValue === 5) {
getOrgs()
}
if (newValue === 6) {
+1 -2
View File
@@ -123,8 +123,7 @@ const Settings = (props) => {
return response.json()
})
.then((responseJson) => {
console.log(responseJson)
.then((responseJson) => {
setUserSettings(responseJson)
})
.catch(error => {