diff --git a/core/api/endpoint/user_endpoint.go b/core/api/endpoint/user_endpoint.go index d2d04269..8d87715c 100644 --- a/core/api/endpoint/user_endpoint.go +++ b/core/api/endpoint/user_endpoint.go @@ -718,7 +718,7 @@ func ResetUserPassword(w http.ResponseWriter, r *http.Request) { log.IfErr(err) } -// Get user object contain associated accounts but credentials are wiped. +// GetSecuredUser wipes credentials. func GetSecuredUser(p request.Persister, orgID, user string) (u entity.User, err error) { u, err = p.GetUser(user) AttachUserAccounts(p, orgID, &u) @@ -726,6 +726,7 @@ func GetSecuredUser(p request.Persister, orgID, user string) (u entity.User, err return } +// AttachUserAccounts ... func AttachUserAccounts(p request.Persister, orgID string, user *entity.User) { user.ProtectSecrets() a, err := p.GetUserAccounts(user.RefID) diff --git a/core/authentication/endpoint.go b/core/authentication/endpoint.go deleted file mode 100644 index e69de29b..00000000 diff --git a/core/request/context.go b/core/request/context.go deleted file mode 100644 index 29a8e145..00000000 --- a/core/request/context.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2016 Documize Inc. . All rights reserved. -// -// This software (Documize Community Edition) is licensed under -// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html -// -// You can operate outside the AGPL restrictions by purchasing -// Documize Enterprise Edition and obtaining a commercial license -// by contacting . -// -// https://documize.com - -// Package request provides HTTP request parsing functions. -package request diff --git a/domain/account/endpoint.go b/domain/account/endpoint.go new file mode 100644 index 00000000..5e7f5f0a --- /dev/null +++ b/domain/account/endpoint.go @@ -0,0 +1,12 @@ +package account + +import ( + "github.com/documize/community/core/env" + "github.com/documize/community/domain" +) + +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store domain.Store +} diff --git a/domain/account/store.go b/domain/account/mysql/store.go similarity index 64% rename from domain/account/store.go rename to domain/account/mysql/store.go index 8b00a5bd..45839e1f 100644 --- a/domain/account/store.go +++ b/domain/account/mysql/store.go @@ -1,22 +1,40 @@ -package account +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package mysql import ( "database/sql" "fmt" "time" + "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" "github.com/documize/community/domain/store/mysql" + "github.com/documize/community/model/account" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // Add inserts the given record into the datbase account table. -func Add(s domain.StoreContext, account Account) (err error) { +func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err error) { account.Created = time.Now().UTC() account.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + stmt, err := ctx.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) if err != nil { @@ -35,7 +53,7 @@ func Add(s domain.StoreContext, account Account) (err error) { } // GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion. -func GetUserAccount(s domain.StoreContext, userID string) (account Account, err error) { +func (s Scope) GetUserAccount(ctx domain.RequestContext, userID string) (account account.Account, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE b.refid=a.orgid and a.orgid=? and a.userid=?") defer streamutil.Close(stmt) @@ -44,7 +62,7 @@ func GetUserAccount(s domain.StoreContext, userID string) (account Account, err return } - err = stmt.Get(&account, s.Context.OrgID, userID) + err = stmt.Get(&account, ctx.OrgID, userID) if err != sql.ErrNoRows && err != nil { err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID)) return @@ -54,7 +72,7 @@ func GetUserAccount(s domain.StoreContext, userID string) (account Account, err } // GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order. -func GetUserAccounts(s domain.StoreContext, userID string) (t []Account, err error) { +func (s Scope) GetUserAccounts(ctx domain.RequestContext, userID string) (t []account.Account, err error) { err = s.Runtime.Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title", userID) if err != sql.ErrNoRows && err != nil { @@ -65,19 +83,19 @@ func GetUserAccounts(s domain.StoreContext, userID string) (t []Account, err err } // GetAccountsByOrg returns a slice of database account records, for all users in the client's organization. -func GetAccountsByOrg(s domain.StoreContext) (t []Account, err error) { - err = s.Runtime.Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1", s.Context.OrgID) +func (s Scope) GetAccountsByOrg(ctx domain.RequestContext) (t []account.Account, err error) { + err = s.Runtime.Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1", ctx.OrgID) if err != sql.ErrNoRows && err != nil { - err = errors.Wrap(err, fmt.Sprintf("execute select account for org %s", s.Context.OrgID)) + err = errors.Wrap(err, fmt.Sprintf("execute select account for org %s", ctx.OrgID)) } return } // CountOrgAccounts returns the numnber of active user accounts for specified organization. -func CountOrgAccounts(s domain.StoreContext) (c int) { - row := s.Runtime.Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? AND active=1", s.Context.OrgID) +func (s Scope) CountOrgAccounts(ctx domain.RequestContext) (c int) { + row := s.Runtime.Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? AND active=1", ctx.OrgID) err := row.Scan(&c) @@ -94,10 +112,10 @@ func CountOrgAccounts(s domain.StoreContext) (c int) { } // UpdateAccount updates the database record for the given account to the given values. -func UpdateAccount(s domain.StoreContext, account Account) (err error) { +func (s Scope) UpdateAccount(ctx domain.RequestContext, account account.Account) (err error) { account.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid") + stmt, err := ctx.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid") defer streamutil.Close(stmt) if err != nil { @@ -115,7 +133,7 @@ func UpdateAccount(s domain.StoreContext, account Account) (err error) { } // HasOrgAccount returns if the given orgID has valid userID. -func HasOrgAccount(s domain.StoreContext, orgID, userID string) bool { +func (s Scope) HasOrgAccount(ctx domain.RequestContext, orgID, userID string) bool { row := s.Runtime.Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? and userid=?", orgID, userID) var count int @@ -138,7 +156,7 @@ func HasOrgAccount(s domain.StoreContext, orgID, userID string) bool { } // DeleteAccount deletes the database record in the account table for user ID. -func DeleteAccount(s domain.StoreContext, ID string) (rows int64, err error) { +func (s Scope) DeleteAccount(ctx domain.RequestContext, ID string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.DeleteConstrained(s.Context.Transaction, "account", s.Context.OrgID, ID) + return b.DeleteConstrained(ctx.Transaction, "account", ctx.OrgID, ID) } diff --git a/domain/eventing/store.go b/domain/audit/mysql/store.go similarity index 74% rename from domain/eventing/store.go rename to domain/audit/mysql/store.go index 8ab82e99..c7ec6c0d 100644 --- a/domain/eventing/store.go +++ b/domain/audit/mysql/store.go @@ -9,23 +9,30 @@ // // https://documize.com -// Package eventing records user events. -package eventing +// Package audit records user events. +package audit import ( "time" + "github.com/documize/community/core/env" "github.com/documize/community/domain" + "github.com/documize/community/model/audit" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // Record adds event entry for specified user. -func Record(s domain.StoreContext, t EventType) { - e := AppEvent{} - e.OrgID = s.Context.OrgID - e.UserID = s.Context.UserID +func (s Scope) Record(ctx domain.RequestContext, t audit.EventType) { + e := audit.AppEvent{} + e.OrgID = ctx.OrgID + e.UserID = ctx.UserID e.Created = time.Now().UTC() - e.IP = s.Context.ClientIP + e.IP = ctx.ClientIP e.Type = string(t) tx, err := s.Runtime.Db.Beginx() diff --git a/domain/auth/endpoint.go b/domain/auth/endpoint.go index 89ed7629..2dd070c6 100644 --- a/domain/auth/endpoint.go +++ b/domain/auth/endpoint.go @@ -11,20 +11,7 @@ package auth -import ( - "database/sql" - "errors" - "net/http" - "strings" - - "github.com/documize/community/core/response" - "github.com/documize/community/core/secrets" - "github.com/documize/community/domain" - "github.com/documize/community/domain/organization" - "github.com/documize/community/domain/section/provider" - "github.com/documize/community/domain/user" -) - +/* // Authenticate user based up HTTP Authorization header. // An encrypted authentication token is issued with an expiry date. func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) { @@ -205,3 +192,4 @@ func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) { response.WriteJSON(w, u) return } +*/ diff --git a/domain/auth/model.go b/domain/auth/model.go index c4e8cbd8..4e0f02ef 100644 --- a/domain/auth/model.go +++ b/domain/auth/model.go @@ -11,11 +11,7 @@ package auth -import ( - "github.com/documize/community/core/env" - "github.com/documize/community/domain/user" -) - +/* // Handler contains the runtime information such as logging and database. type Handler struct { Runtime *env.Runtime @@ -26,3 +22,4 @@ type AuthenticationModel struct { Token string `json:"token"` User user.User `json:"user"` } +*/ diff --git a/domain/context.go b/domain/context.go index f0658148..e2689592 100644 --- a/domain/context.go +++ b/domain/context.go @@ -1,3 +1,15 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +// Package domain defines data structures for moving data between services. package domain import ( @@ -5,7 +17,6 @@ import ( "net/http" "time" - "github.com/documize/community/core/env" "github.com/jmoiron/sqlx" ) @@ -51,13 +62,13 @@ func GetRequestContext(r *http.Request) RequestContext { return r.Context().Value(DocumizeContextKey).(RequestContext) } -// StoreContext provides data persistence methods with runtime and request context. -type StoreContext struct { - Runtime *env.Runtime - Context RequestContext -} +// // Scope provides data persistence methods with runtime and request context. +// type Scope struct { +// Runtime *env.Runtime +// Context RequestContext +// } -// NewContext returns request scoped user context and store context for persistence logic. -func NewContext(rt *env.Runtime, r *http.Request) StoreContext { - return StoreContext{Runtime: rt, Context: GetRequestContext(r)} -} +// // NewScope returns request scoped user context and store context for persistence logic. +// func NewScope(rt *env.Runtime, r *http.Request) Scope { +// return Scope{Runtime: rt, Context: GetRequestContext(r)} +// } diff --git a/domain/document/store.go b/domain/document/mysql/store.go similarity index 71% rename from domain/document/store.go rename to domain/document/mysql/store.go index 2312c160..6bdb6b02 100644 --- a/domain/document/store.go +++ b/domain/document/mysql/store.go @@ -14,14 +14,20 @@ package document import ( "fmt" + "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // MoveDocumentSpace changes the label for client's organization's documents which have space "id", to "move". -func MoveDocumentSpace(s domain.StoreContext, id, move string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?") +func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?") defer streamutil.Close(stmt) if err != nil { @@ -29,7 +35,7 @@ func MoveDocumentSpace(s domain.StoreContext, id, move string) (err error) { return } - _, err = stmt.Exec(move, s.Context.OrgID, id) + _, err = stmt.Exec(move, ctx.OrgID, id) if err != nil { err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id)) return diff --git a/domain/organization/endpoint.go b/domain/organization/endpoint.go new file mode 100644 index 00000000..e99a24db --- /dev/null +++ b/domain/organization/endpoint.go @@ -0,0 +1,94 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package organization + +import ( + "database/sql" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/documize/community/core/env" + "github.com/documize/community/core/request" + "github.com/documize/community/core/response" + "github.com/documize/community/core/streamutil" + "github.com/documize/community/domain" + "github.com/documize/community/model/org" +) + +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store *domain.Store +} + +// Get returns the requested organization. +func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { + method := "org.Get" + ctx := domain.GetRequestContext(r) + + orgID := request.Param(r, "orgID") + + if orgID != ctx.OrgID { + response.WriteForbiddenError(w) + return + } + + org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID) + if err != nil && err != sql.ErrNoRows { + response.WriteServerError(w, method, err) + return + } + + response.WriteJSON(w, org) +} + +// Update saves organization amends. +func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { + method := "org.Update" + ctx := domain.GetRequestContext(r) + + if !ctx.Administrator { + response.WriteForbiddenError(w) + return + } + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + + if err != nil { + response.WriteServerError(w, method, err) + return + } + + var org = org.Organization{} + err = json.Unmarshal(body, &org) + + org.RefID = ctx.OrgID + + ctx.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + err = h.Store.Organization.UpdateOrganization(ctx, org) + if err != nil { + ctx.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + ctx.Transaction.Commit() + + response.WriteJSON(w, org) +} diff --git a/domain/organization/store.go b/domain/organization/mysql/store.go similarity index 76% rename from domain/organization/store.go rename to domain/organization/mysql/store.go index 17397cc0..08ff8ef9 100644 --- a/domain/organization/store.go +++ b/domain/organization/mysql/store.go @@ -21,16 +21,22 @@ import ( "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" "github.com/documize/community/domain/store/mysql" + "github.com/documize/community/model/org" "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // AddOrganization inserts the passed organization record into the organization table. -func AddOrganization(s domain.StoreContext, org Organization) error { +func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization) error { org.Created = time.Now().UTC() org.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.Preparex( + stmt, err := ctx.Transaction.Preparex( "INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) @@ -51,7 +57,7 @@ func AddOrganization(s domain.StoreContext, org Organization) error { } // GetOrganization returns the Organization reocrod from the organization database table with the given id. -func GetOrganization(s domain.StoreContext, id string) (org Organization, err error) { +func (s Scope) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE refid=?") defer streamutil.Close(stmt) @@ -71,7 +77,7 @@ func GetOrganization(s domain.StoreContext, id string) (org Organization, err er } // GetOrganizationByDomain returns the organization matching a given URL subdomain. -func GetOrganizationByDomain(s domain.StoreContext, subdomain string) (org Organization, err error) { +func (s Scope) GetOrganizationByDomain(ctx domain.RequestContext, subdomain string) (org org.Organization, err error) { err = nil subdomain = strings.ToLower(subdomain) @@ -98,10 +104,10 @@ func GetOrganizationByDomain(s domain.StoreContext, subdomain string) (org Organ } // UpdateOrganization updates the given organization record in the database to the values supplied. -func UpdateOrganization(s domain.StoreContext, org Organization) (err error) { +func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) { org.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid") + stmt, err := ctx.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid") defer streamutil.Close(stmt) if err != nil { @@ -119,14 +125,14 @@ func UpdateOrganization(s domain.StoreContext, org Organization) (err error) { } // DeleteOrganization deletes the orgID organization from the organization table. -func DeleteOrganization(s domain.StoreContext, orgID string) (rows int64, err error) { +func (s Scope) DeleteOrganization(ctx domain.RequestContext, orgID string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.Delete(s.Context.Transaction, "organization", orgID) + return b.Delete(ctx.Transaction, "organization", orgID) } // RemoveOrganization sets the orgID organization to be inactive, thus executing a "soft delete" operation. -func RemoveOrganization(s domain.StoreContext, rc domain.RequestContext, orgID string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?") +func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?") defer streamutil.Close(stmt) if err != nil { @@ -144,10 +150,10 @@ func RemoveOrganization(s domain.StoreContext, rc domain.RequestContext, orgID s } // UpdateAuthConfig updates the given organization record in the database with the auth config details. -func UpdateAuthConfig(s domain.StoreContext, org Organization) (err error) { +func (s Scope) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) { org.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.PrepareNamed("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid") + stmt, err := ctx.Transaction.PrepareNamed("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid") defer streamutil.Close(stmt) if err != nil { @@ -165,7 +171,7 @@ func UpdateAuthConfig(s domain.StoreContext, org Organization) (err error) { } // CheckDomain makes sure there is an organisation with the correct domain -func CheckDomain(s domain.StoreContext, domain string) string { +func (s Scope) CheckDomain(ctx domain.RequestContext, domain string) string { row := s.Runtime.Db.QueryRow("SELECT COUNT(*) FROM organization WHERE domain=? AND active=1", domain) var count int diff --git a/domain/organization/organization.go b/domain/organization/organization.go index 48433510..e52013d9 100644 --- a/domain/organization/organization.go +++ b/domain/organization/organization.go @@ -14,22 +14,19 @@ package organization import ( "net/http" "strings" - - "github.com/documize/community/domain" ) // GetRequestSubdomain extracts subdomain from referring URL. -func GetRequestSubdomain(s domain.StoreContext, r *http.Request) string { - return urlSubdomain(s, r.Referer()) +func GetRequestSubdomain(r *http.Request) string { + return urlSubdomain(r.Referer()) } // GetSubdomainFromHost extracts the subdomain from the requesting URL. -func GetSubdomainFromHost(s domain.StoreContext, r *http.Request) string { - return urlSubdomain(s, r.Host) +func GetSubdomainFromHost(r *http.Request) string { + return urlSubdomain(r.Host) } -// Find the subdomain (which is actually the organisation). -func urlSubdomain(s domain.StoreContext, url string) string { +func urlSubdomain(url string) string { url = strings.ToLower(url) url = strings.Replace(url, "https://", "", 1) url = strings.Replace(url, "http://", "", 1) @@ -42,5 +39,5 @@ func urlSubdomain(s domain.StoreContext, url string) string { url = "" } - return CheckDomain(s, url) + return url } diff --git a/domain/pin/endpoint.go b/domain/pin/endpoint.go index e4c66ef5..4419ae30 100644 --- a/domain/pin/endpoint.go +++ b/domain/pin/endpoint.go @@ -18,17 +18,25 @@ import ( "net/http" "strings" + "github.com/documize/community/core/env" "github.com/documize/community/core/request" "github.com/documize/community/core/response" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" - "github.com/documize/community/domain/eventing" + "github.com/documize/community/model/audit" + "github.com/documize/community/model/pin" ) +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store *domain.Store +} + // Add saves pinned item. func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { method := "pin.Add" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) userID := request.Param(r, "userID") if len(userID) == 0 { @@ -41,7 +49,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } - if !s.Context.Authenticated { + if !ctx.Authenticated { response.WriteForbiddenError(w) return } @@ -53,7 +61,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } - var pin Pin + var pin pin.Pin err = json.Unmarshal(body, &pin) if err != nil { response.WriteBadRequestError(w, method, "pin") @@ -61,31 +69,31 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { } pin.RefID = uniqueid.Generate() - pin.OrgID = s.Context.OrgID - pin.UserID = s.Context.UserID + pin.OrgID = ctx.OrgID + pin.UserID = ctx.UserID pin.Pin = strings.TrimSpace(pin.Pin) if len(pin.Pin) > 20 { pin.Pin = pin.Pin[0:20] } - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - err = Add(s, pin) + err = h.Store.Pin.Add(ctx, pin) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypePinAdd) + h.Store.Audit.Record(ctx, audit.EventTypePinAdd) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() - newPin, err := GetPin(s, pin.RefID) + newPin, err := h.Store.Pin.GetPin(ctx, pin.RefID) if err != nil { response.WriteServerError(w, method, err) return @@ -97,7 +105,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { // GetUserPins returns users' pins. func (h *Handler) GetUserPins(w http.ResponseWriter, r *http.Request) { method := "pin.GetUserPins" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) userID := request.Param(r, "userID") if len(userID) == 0 { @@ -105,19 +113,19 @@ func (h *Handler) GetUserPins(w http.ResponseWriter, r *http.Request) { return } - if !s.Context.Authenticated { + if !ctx.Authenticated { response.WriteForbiddenError(w) return } - pins, err := GetUserPins(s, userID) + pins, err := h.Store.Pin.GetUserPins(ctx, userID) if err != nil && err != sql.ErrNoRows { response.WriteServerError(w, method, err) return } if err == sql.ErrNoRows { - pins = []Pin{} + pins = []pin.Pin{} } response.WriteJSON(w, pins) @@ -126,7 +134,7 @@ func (h *Handler) GetUserPins(w http.ResponseWriter, r *http.Request) { // DeleteUserPin removes saved user pin. func (h *Handler) DeleteUserPin(w http.ResponseWriter, r *http.Request) { method := "pin.DeleteUserPin" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) userID := request.Param(r, "userID") if len(userID) == 0 { @@ -145,28 +153,28 @@ func (h *Handler) DeleteUserPin(w http.ResponseWriter, r *http.Request) { return } - if s.Context.UserID != userID { + if ctx.UserID != userID { response.WriteForbiddenError(w) return } var err error - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - _, err = DeletePin(s, pinID) + _, err = h.Store.Pin.DeletePin(ctx, pinID) if err != nil && err != sql.ErrNoRows { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypePinDelete) + h.Store.Audit.Record(ctx, audit.EventTypePinDelete) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteEmpty(w) } @@ -174,7 +182,7 @@ func (h *Handler) DeleteUserPin(w http.ResponseWriter, r *http.Request) { // UpdatePinSequence records order of pinned items. func (h *Handler) UpdatePinSequence(w http.ResponseWriter, r *http.Request) { method := "pin.DeleteUserPin" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) userID := request.Param(r, "userID") if len(userID) == 0 { @@ -187,7 +195,7 @@ func (h *Handler) UpdatePinSequence(w http.ResponseWriter, r *http.Request) { return } - if !s.Context.Authenticated { + if !ctx.Authenticated { response.WriteForbiddenError(w) return } @@ -207,26 +215,26 @@ func (h *Handler) UpdatePinSequence(w http.ResponseWriter, r *http.Request) { return } - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } for k, v := range pins { - err = UpdatePinSequence(s, v, k+1) + err = h.Store.Pin.UpdatePinSequence(ctx, v, k+1) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } } - eventing.Record(s, eventing.EventTypePinResequence) + h.Store.Audit.Record(ctx, audit.EventTypePinResequence) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() - newPins, err := GetUserPins(s, userID) + newPins, err := h.Store.Pin.GetUserPins(ctx, userID) if err != nil { response.WriteServerError(w, method, err) return diff --git a/domain/pin/store.go b/domain/pin/mysql/store.go similarity index 59% rename from domain/pin/store.go rename to domain/pin/mysql/store.go index e369d315..cf9a3db1 100644 --- a/domain/pin/store.go +++ b/domain/pin/mysql/store.go @@ -15,17 +15,23 @@ import ( "fmt" "time" - "github.com/documize/community/core/api/entity" + "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" "github.com/documize/community/domain/store/mysql" + "github.com/documize/community/model/pin" "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // Add saves pinned item. -func Add(s domain.StoreContext, pin Pin) (err error) { - row := s.Runtime.Db.QueryRow("SELECT max(sequence) FROM pin WHERE orgid=? AND userid=?", s.Context.OrgID, s.Context.UserID) +func (s Scope) Add(ctx domain.RequestContext, pin pin.Pin) (err error) { + row := s.Runtime.Db.QueryRow("SELECT max(sequence) FROM pin WHERE orgid=? AND userid=?", ctx.OrgID, ctx.UserID) var maxSeq int err = row.Scan(&maxSeq) @@ -37,7 +43,7 @@ func Add(s domain.StoreContext, pin Pin) (err error) { pin.Revised = time.Now().UTC() pin.Sequence = maxSeq + 1 - stmt, err := s.Context.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") + stmt, err := ctx.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) if err != nil { @@ -55,7 +61,7 @@ func Add(s domain.StoreContext, pin Pin) (err error) { } // GetPin returns requested pinned item. -func GetPin(s domain.StoreContext, id string) (pin Pin, err error) { +func (s Scope) GetPin(ctx domain.RequestContext, id string) (pin pin.Pin, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?") defer streamutil.Close(stmt) @@ -64,7 +70,7 @@ func GetPin(s domain.StoreContext, id string) (pin Pin, err error) { return } - err = stmt.Get(&pin, s.Context.OrgID, id) + err = stmt.Get(&pin, ctx.OrgID, id) if err != nil { err = errors.Wrap(err, fmt.Sprintf("execute select for pin %s", id)) return @@ -74,11 +80,11 @@ func GetPin(s domain.StoreContext, id string) (pin Pin, err error) { } // GetUserPins returns pinned items for specified user. -func GetUserPins(s domain.StoreContext, userID string) (pins []Pin, err error) { - err = s.Runtime.Db.Select(&pins, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND userid=? ORDER BY sequence", s.Context.OrgID, userID) +func (s Scope) GetUserPins(ctx domain.RequestContext, userID string) (pins []pin.Pin, err error) { + err = s.Runtime.Db.Select(&pins, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND userid=? ORDER BY sequence", ctx.OrgID, userID) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("execute select pins for org %s and user %s", s.Context.OrgID, userID)) + err = errors.Wrap(err, fmt.Sprintf("execute select pins for org %s and user %s", ctx.OrgID, userID)) return } @@ -86,11 +92,11 @@ func GetUserPins(s domain.StoreContext, userID string) (pins []Pin, err error) { } // UpdatePin updates existing pinned item. -func UpdatePin(s domain.StoreContext, pin entity.Pin) (err error) { +func (s Scope) UpdatePin(ctx domain.RequestContext, pin pin.Pin) (err error) { pin.Revised = time.Now().UTC() var stmt *sqlx.NamedStmt - stmt, err = s.Context.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid") + stmt, err = ctx.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid") defer streamutil.Close(stmt) if err != nil { @@ -108,8 +114,8 @@ func UpdatePin(s domain.StoreContext, pin entity.Pin) (err error) { } // UpdatePinSequence updates existing pinned item sequence number -func UpdatePinSequence(s domain.StoreContext, pinID string, sequence int) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?") +func (s Scope) UpdatePinSequence(ctx domain.RequestContext, pinID string, sequence int) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?") defer streamutil.Close(stmt) if err != nil { @@ -117,7 +123,7 @@ func UpdatePinSequence(s domain.StoreContext, pinID string, sequence int) (err e return } - _, err = stmt.Exec(sequence, time.Now().UTC(), s.Context.OrgID, s.Context.UserID, pinID) + _, err = stmt.Exec(sequence, time.Now().UTC(), ctx.OrgID, ctx.UserID, pinID) if err != nil { err = errors.Wrap(err, fmt.Sprintf("execute pin sequence update %s", pinID)) return @@ -127,19 +133,19 @@ func UpdatePinSequence(s domain.StoreContext, pinID string, sequence int) (err e } // DeletePin removes folder from the store. -func DeletePin(s domain.StoreContext, id string) (rows int64, err error) { +func (s Scope) DeletePin(ctx domain.RequestContext, id string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.DeleteConstrained(s.Context.Transaction, "pin", s.Context.OrgID, id) + return b.DeleteConstrained(ctx.Transaction, "pin", ctx.OrgID, id) } // DeletePinnedSpace removes any pins for specified space. -func DeletePinnedSpace(s domain.StoreContext, spaceID string) (rows int64, err error) { +func (s Scope) DeletePinnedSpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.DeleteWhere(s.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND labelid=\"%s\"", s.Context.OrgID, spaceID)) + return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND labelid=\"%s\"", ctx.OrgID, spaceID)) } // DeletePinnedDocument removes any pins for specified document. -func DeletePinnedDocument(s domain.StoreContext, documentID string) (rows int64, err error) { +func (s Scope) DeletePinnedDocument(ctx domain.RequestContext, documentID string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.DeleteWhere(s.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND documentid=\"%s\"", s.Context.OrgID, documentID)) + return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND documentid=\"%s\"", ctx.OrgID, documentID)) } diff --git a/domain/space/endpoint.go b/domain/space/endpoint.go index a4ef031a..e700c87e 100644 --- a/domain/space/endpoint.go +++ b/domain/space/endpoint.go @@ -23,6 +23,7 @@ import ( "github.com/documize/api/wordsmith/log" "github.com/documize/community/core/api/mail" + "github.com/documize/community/core/env" "github.com/documize/community/core/request" "github.com/documize/community/core/response" "github.com/documize/community/core/secrets" @@ -30,25 +31,29 @@ import ( "github.com/documize/community/core/stringutil" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" - "github.com/documize/community/domain/account" - "github.com/documize/community/domain/document" - "github.com/documize/community/domain/eventing" - "github.com/documize/community/domain/organization" - "github.com/documize/community/domain/pin" - "github.com/documize/community/domain/user" + "github.com/documize/community/model/account" + "github.com/documize/community/model/audit" + "github.com/documize/community/model/space" + "github.com/documize/community/model/user" ) +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store *domain.Store +} + // Add creates a new space. func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { - method := "AddSpace" - s := domain.NewContext(h.Runtime, r) + method := "space.Add" + ctx := domain.GetRequestContext(r) if !h.Runtime.Product.License.IsValid() { response.WriteBadLicense(w) return } - if !s.Context.Editor { + if !ctx.Editor { response.WriteForbiddenError(w) return } @@ -60,7 +65,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } - var space = Space{} + var space = space.Space{} err = json.Unmarshal(body, &space) if err != nil { response.WriteServerError(w, method, err) @@ -72,27 +77,27 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { return } - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } space.RefID = uniqueid.Generate() - space.OrgID = s.Context.OrgID + space.OrgID = ctx.OrgID - err = addSpace(s, space) + err = h.Store.Space.Add(ctx, space) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpaceAdd) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceAdd) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() - space, _ = Get(s, space.RefID) + space, _ = h.Store.Space.Get(ctx, space.RefID) response.WriteJSON(w, space) } @@ -100,7 +105,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { // Get returns the requested space. func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { method := "Get" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) id := request.Param(r, "folderID") if len(id) == 0 { @@ -108,7 +113,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { return } - sp, err := Get(s, id) + sp, err := h.Store.Space.Get(ctx, id) if err == sql.ErrNoRows { response.WriteNotFoundError(w, method, id) return @@ -124,16 +129,16 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { // GetAll returns spaces the user can see. func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) { method := "GetAll" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) - sp, err := GetAll(s) + sp, err := h.Store.Space.GetAll(ctx) if err != nil && err != sql.ErrNoRows { response.WriteServerError(w, method, err) return } if len(sp) == 0 { - sp = []Space{} + sp = []space.Space{} } response.WriteJSON(w, sp) @@ -141,17 +146,17 @@ func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) { // GetSpaceViewers returns the users that can see the shared spaces. func (h *Handler) GetSpaceViewers(w http.ResponseWriter, r *http.Request) { - method := "GetSpaceViewers" - s := domain.NewContext(h.Runtime, r) + method := "space.Viewers" + ctx := domain.GetRequestContext(r) - v, err := Viewers(s) + v, err := h.Store.Space.Viewers(ctx) if err != nil && err != sql.ErrNoRows { response.WriteServerError(w, method, err) return } if len(v) == 0 { - v = []Viewer{} + v = []space.Viewer{} } response.WriteJSON(w, v) @@ -160,9 +165,9 @@ func (h *Handler) GetSpaceViewers(w http.ResponseWriter, r *http.Request) { // Update processes request to save space object to the database func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { method := "space.Update" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) - if !s.Context.Editor { + if !ctx.Editor { response.WriteForbiddenError(w) return } @@ -180,7 +185,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { return } - var sp Space + var sp space.Space err = json.Unmarshal(body, &sp) if err != nil { response.WriteBadRequestError(w, method, "marshal") @@ -194,22 +199,22 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { sp.RefID = folderID - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - err = Update(s, sp) + err = h.Store.Space.Update(ctx, sp) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpaceUpdate) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceUpdate) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteJSON(w, sp) } @@ -217,68 +222,68 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { // Remove moves documents to another folder before deleting it func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) { method := "space.Remove" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) if !h.Runtime.Product.License.IsValid() { response.WriteBadLicense(w) return } - if !s.Context.Editor { + if !ctx.Editor { response.WriteForbiddenError(w) return } id := request.Param(r, "folderID") - move := request.Param(r, "moveToId") - if len(id) == 0 { response.WriteMissingDataError(w, method, "folderID") return } + + move := request.Param(r, "moveToId") if len(move) == 0 { response.WriteMissingDataError(w, method, "moveToId") return } var err error - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - _, err = Delete(s, id) + _, err = h.Store.Space.Delete(ctx, id) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - err = document.MoveDocumentSpace(s, id, move) + err = h.Store.Document.MoveDocumentSpace(ctx, id, move) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - err = MoveSpaceRoles(s, id, move) + err = h.Store.Space.MoveSpaceRoles(ctx, id, move) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - _, err = pin.DeletePinnedSpace(s, id) + _, err = h.Store.Pin.DeletePinnedSpace(ctx, id) if err != nil && err != sql.ErrNoRows { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpaceDelete) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteEmpty(w) } @@ -286,14 +291,14 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) { // Delete deletes empty space. func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { method := "space.Delete" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) if !h.Runtime.Product.License.IsValid() { response.WriteBadLicense(w) return } - if !s.Context.Editor { + if !ctx.Editor { response.WriteForbiddenError(w) return } @@ -305,45 +310,46 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { } var err error - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - _, err = Delete(s, id) + _, err = h.Store.Space.Delete(ctx, id) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - _, err = DeleteSpaceRoles(s, id) + _, err = h.Store.Space.DeleteSpaceRoles(ctx, id) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - _, err = pin.DeletePinnedSpace(s, id) + _, err = h.Store.Pin.DeletePinnedSpace(ctx, id) if err != nil && err != sql.ErrNoRows { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpaceDelete) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete) + + ctx.Transaction.Commit() - s.Context.Transaction.Commit() response.WriteEmpty(w) } // SetPermissions persists specified spac3 permissions func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { method := "space.SetPermissions" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) - if !s.Context.Editor { + if !ctx.Editor { response.WriteForbiddenError(w) return } @@ -354,13 +360,13 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { return } - sp, err := Get(s, id) + sp, err := h.Store.Space.Get(ctx, id) if err != nil { response.WriteNotFoundError(w, method, "No such space") return } - if sp.UserID != s.Context.UserID { + if sp.UserID != ctx.UserID { response.WriteForbiddenError(w) return } @@ -372,14 +378,14 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { return } - var model = RolesModel{} + var model = space.RolesModel{} err = json.Unmarshal(body, &model) if err != nil { response.WriteServerError(w, method, err) return } - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return @@ -387,9 +393,9 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { // We compare new permisions to what we had before. // Why? So we can send out folder invitation emails. - previousRoles, err := GetRoles(s, id) + previousRoles, err := h.Store.Space.GetRoles(ctx, id) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } @@ -402,17 +408,17 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { } // Who is sharing this folder? - inviter, err := user.Get(s, s.Context.UserID) + inviter, err := h.Store.User.Get(ctx, ctx.UserID) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } // Nuke all previous permissions for this folder - _, err = DeleteSpaceRoles(s, id) + _, err = h.Store.Space.DeleteSpaceRoles(ctx, id) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } @@ -421,14 +427,14 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { hasEveryoneRole := false roleCount := 0 - url := s.Context.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) + url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) for _, role := range model.Roles { - role.OrgID = s.Context.OrgID + role.OrgID = ctx.OrgID role.LabelID = id // Ensure the folder owner always has access! - if role.UserID == s.Context.UserID { + if role.UserID == ctx.UserID { me = true role.CanView = true role.CanEdit = true @@ -442,7 +448,7 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { if role.CanView || role.CanEdit { roleID := uniqueid.Generate() role.RefID = roleID - err = AddRole(s, role) + err = h.Store.Space.AddRole(ctx, role) roleCount++ log.IfErr(err) @@ -453,7 +459,7 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { // we skip 'everyone' (user id != empty string) if len(role.UserID) > 0 { var existingUser user.User - existingUser, err = user.Get(s, role.UserID) + existingUser, err = h.Store.User.Get(ctx, role.UserID) if err == nil { go mail.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message) @@ -468,18 +474,18 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { // Do we need to ensure permissions for space owner when shared? if !me { - role := Role{} + role := space.Role{} role.LabelID = id - role.OrgID = s.Context.OrgID - role.UserID = s.Context.UserID + role.OrgID = ctx.OrgID + role.UserID = ctx.UserID role.CanEdit = true role.CanView = true roleID := uniqueid.Generate() role.RefID = roleID - err = AddRole(s, role) + err = h.Store.Space.AddRole(ctx, role) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } @@ -487,25 +493,25 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { // Mark up folder type as either public, private or restricted access. if hasEveryoneRole { - sp.Type = ScopePublic + sp.Type = space.ScopePublic } else { if roleCount > 1 { - sp.Type = ScopeRestricted + sp.Type = space.ScopeRestricted } else { - sp.Type = ScopePrivate + sp.Type = space.ScopePrivate } } - err = Update(s, sp) + err = h.Store.Space.Update(ctx, sp) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpacePermission) + h.Store.Audit.Record(ctx, audit.EventTypeSpacePermission) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteEmpty(w) } @@ -513,7 +519,7 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { // GetPermissions returns user permissions for the requested folder. func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) { method := "space.GetPermissions" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) folderID := request.Param(r, "folderID") if len(folderID) == 0 { @@ -521,14 +527,14 @@ func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) { return } - roles, err := GetRoles(s, folderID) + roles, err := h.Store.Space.GetRoles(ctx, folderID) if err != nil && err != sql.ErrNoRows { response.WriteServerError(w, method, err) return } if len(roles) == 0 { - roles = []Role{} + roles = []space.Role{} } response.WriteJSON(w, roles) @@ -537,7 +543,7 @@ func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) { // AcceptInvitation records the fact that a user has completed space onboard process. func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { method := "space.AcceptInvitation" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) folderID := request.Param(r, "folderID") if len(folderID) == 0 { @@ -545,14 +551,14 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { return } - org, err := organization.GetOrganizationByDomain(s, s.Context.Subdomain) + org, err := h.Store.Organization.GetOrganizationByDomain(ctx, ctx.Subdomain) if err != nil { response.WriteServerError(w, method, err) return } // AcceptShare does not authenticate the user hence the context needs to set up - s.Context.OrgID = org.RefID + ctx.OrgID = org.RefID defer streamutil.Close(r.Body) body, err := ioutil.ReadAll(r.Body) @@ -561,7 +567,7 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { return } - var model = AcceptShareModel{} + var model = space.AcceptShareModel{} err = json.Unmarshal(body, &model) if err != nil { response.WriteBadRequestError(w, method, err.Error()) @@ -573,44 +579,44 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { return } - u, err := user.GetBySerial(s, model.Serial) + u, err := h.Store.User.GetBySerial(ctx, model.Serial) if err != nil && err == sql.ErrNoRows { response.WriteDuplicateError(w, method, "user") return } // AcceptShare does not authenticate the user hence the context needs to set up - s.Context.UserID = u.RefID + ctx.UserID = u.RefID u.Firstname = model.Firstname u.Lastname = model.Lastname u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname) - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - err = user.UpdateUser(s, u) + err = h.Store.User.UpdateUser(ctx, u) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } salt := secrets.GenerateSalt() - err = user.UpdateUserPassword(s, u.RefID, salt, secrets.GeneratePassword(model.Password, salt)) + err = h.Store.User.UpdateUserPassword(ctx, u.RefID, salt, secrets.GeneratePassword(model.Password, salt)) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - eventing.Record(s, eventing.EventTypeSpaceJoin) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceJoin) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteJSON(w, u) } @@ -618,7 +624,7 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { // Invite sends users folder invitation emails. func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { method := "space.Invite" - s := domain.NewContext(h.Runtime, r) + ctx := domain.GetRequestContext(r) id := request.Param(r, "folderID") if len(id) == 0 { @@ -626,13 +632,13 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { return } - sp, err := Get(s, id) + sp, err := h.Store.Space.Get(ctx, id) if err != nil { response.WriteServerError(w, method, err) return } - if sp.UserID != s.Context.UserID { + if sp.UserID != ctx.UserID { response.WriteForbiddenError(w) return } @@ -644,38 +650,38 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { return } - var model = InvitationModel{} + var model = space.InvitationModel{} err = json.Unmarshal(body, &model) if err != nil { response.WriteBadRequestError(w, method, "json") return } - s.Context.Transaction, err = h.Runtime.Db.Beginx() + ctx.Transaction, err = h.Runtime.Db.Beginx() if err != nil { response.WriteServerError(w, method, err) return } - inviter, err := user.Get(s, s.Context.UserID) + inviter, err := h.Store.User.Get(ctx, ctx.UserID) if err != nil { response.WriteServerError(w, method, err) return } for _, email := range model.Recipients { - u, err := user.GetByEmail(s, email) + u, err := h.Store.User.GetByEmail(ctx, email) if err != nil && err != sql.ErrNoRows { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } if len(u.RefID) > 0 { // Ensure they have access to this organization - accounts, err2 := account.GetUserAccounts(s, u.RefID) + accounts, err2 := h.Store.Account.GetUserAccounts(ctx, u.RefID) if err2 != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } @@ -683,7 +689,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { // we create if they c hasAccess := false for _, a := range accounts { - if a.OrgID == s.Context.OrgID { + if a.OrgID == ctx.OrgID { hasAccess = true } } @@ -691,52 +697,52 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { if !hasAccess { var a account.Account a.UserID = u.RefID - a.OrgID = s.Context.OrgID + a.OrgID = ctx.OrgID a.Admin = false a.Editor = false a.Active = true accountID := uniqueid.Generate() a.RefID = accountID - err = account.Add(s, a) + err = h.Store.Account.Add(ctx, a) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } } // Ensure they have space roles - DeleteUserSpaceRoles(s, sp.RefID, u.RefID) + h.Store.Space.DeleteUserSpaceRoles(ctx, sp.RefID, u.RefID) - role := Role{} + role := space.Role{} role.LabelID = sp.RefID - role.OrgID = s.Context.OrgID + role.OrgID = ctx.OrgID role.UserID = u.RefID role.CanEdit = false role.CanView = true roleID := uniqueid.Generate() role.RefID = roleID - err = AddRole(s, role) + err = h.Store.Space.AddRole(ctx, role) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } - url := s.Context.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) + url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message) h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, email)) } else { // On-board new user if strings.Contains(email, "@") { - url := s.Context.GetAppURL(fmt.Sprintf("auth/share/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) - err = inviteNewUserToSharedSpace(s, email, inviter, url, sp, model.Message) + url := ctx.GetAppURL(fmt.Sprintf("auth/share/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) + err = inviteNewUserToSharedSpace(ctx, h.Store, email, inviter, url, sp, model.Message) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } @@ -747,20 +753,20 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { } // We ensure that the folder is marked as restricted as a minimum! - if len(model.Recipients) > 0 && sp.Type == ScopePrivate { - sp.Type = ScopeRestricted + if len(model.Recipients) > 0 && sp.Type == space.ScopePrivate { + sp.Type = space.ScopeRestricted - err = Update(s, sp) + err = h.Store.Space.Update(ctx, sp) if err != nil { - s.Context.Transaction.Rollback() + ctx.Transaction.Rollback() response.WriteServerError(w, method, err) return } } - eventing.Record(s, eventing.EventTypeSpaceInvite) + h.Store.Audit.Record(ctx, audit.EventTypeSpaceInvite) - s.Context.Transaction.Commit() + ctx.Transaction.Commit() response.WriteEmpty(w) } diff --git a/domain/space/store.go b/domain/space/mysql/store.go similarity index 67% rename from domain/space/store.go rename to domain/space/mysql/store.go index 4bf52983..bb51ddd6 100644 --- a/domain/space/store.go +++ b/domain/space/mysql/store.go @@ -9,28 +9,34 @@ // // https://documize.com -// Package space handles API calls and persistence for spaces. -// Spaces in Documize contain documents. -package space +// Package mysql handles data persistence for spaces. +package mysql import ( "database/sql" "fmt" "time" + "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" "github.com/documize/community/domain/store/mysql" + "github.com/documize/community/model/space" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // Add adds new folder into the store. -func Add(s domain.StoreContext, sp Space) (err error) { - sp.UserID = s.Context.UserID +func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) { + sp.UserID = ctx.UserID sp.Created = time.Now().UTC() sp.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.Preparex("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)") + stmt, err := ctx.Transaction.Preparex("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) if err != nil { @@ -48,7 +54,7 @@ func Add(s domain.StoreContext, sp Space) (err error) { } // Get returns a space from the store. -func Get(s domain.StoreContext, id string) (sp Space, err error) { +func (s Scope) Get(ctx domain.RequestContext, id string) (sp space.Space, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?") defer streamutil.Close(stmt) @@ -57,7 +63,7 @@ func Get(s domain.StoreContext, id string) (sp Space, err error) { return } - err = stmt.Get(&sp, s.Context.OrgID, id) + err = stmt.Get(&sp, ctx.OrgID, id) if err != nil { err = errors.Wrap(err, fmt.Sprintf("unable to execute select for label %s", id)) return @@ -67,7 +73,7 @@ func Get(s domain.StoreContext, id string) (sp Space, err error) { } // PublicSpaces returns folders that anyone can see. -func PublicSpaces(s domain.StoreContext, orgID string) (sp []Space, err error) { +func (s Scope) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error) { sql := "SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=1" err = s.Runtime.Db.Select(&sp, sql, orgID) @@ -82,7 +88,7 @@ func PublicSpaces(s domain.StoreContext, orgID string) (sp []Space, err error) { // GetAll returns folders that the user can see. // Also handles which folders can be seen by anonymous users. -func GetAll(s domain.StoreContext) (sp []Space, err error) { +func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) { sql := ` (SELECT id,refid,label as name,orgid,userid,type,created,revised from label WHERE orgid=? AND type=2 AND userid=?) UNION ALL @@ -94,16 +100,16 @@ UNION ALL ORDER BY name` err = s.Runtime.Db.Select(&sp, sql, - s.Context.OrgID, - s.Context.UserID, - s.Context.OrgID, - s.Context.OrgID, - s.Context.OrgID, - s.Context.OrgID, - s.Context.UserID) + ctx.OrgID, + ctx.UserID, + ctx.OrgID, + ctx.OrgID, + ctx.OrgID, + ctx.OrgID, + ctx.UserID) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("Unable to execute select labels for org %s", s.Context.OrgID)) + err = errors.Wrap(err, fmt.Sprintf("Unable to execute select labels for org %s", ctx.OrgID)) return } @@ -111,10 +117,10 @@ ORDER BY name` } // Update saves space changes. -func Update(s domain.StoreContext, sp Space) (err error) { +func (s Scope) Update(ctx domain.RequestContext, sp space.Space) (err error) { sp.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.PrepareNamed("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid") + stmt, err := ctx.Transaction.PrepareNamed("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid") defer streamutil.Close(stmt) if err != nil { @@ -132,8 +138,8 @@ func Update(s domain.StoreContext, sp Space) (err error) { } // ChangeOwner transfer space ownership. -func ChangeOwner(s domain.StoreContext, currentOwner, newOwner string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?") +func (s Scope) ChangeOwner(ctx domain.RequestContext, currentOwner, newOwner string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?") defer streamutil.Close(stmt) if err != nil { @@ -141,7 +147,7 @@ func ChangeOwner(s domain.StoreContext, currentOwner, newOwner string) (err erro return } - _, err = stmt.Exec(newOwner, currentOwner, s.Context.OrgID) + _, err = stmt.Exec(newOwner, currentOwner, ctx.OrgID) if err != nil { err = errors.Wrap(err, fmt.Sprintf("unable to execute change space owner for %s", currentOwner)) return @@ -151,7 +157,7 @@ func ChangeOwner(s domain.StoreContext, currentOwner, newOwner string) (err erro } // Viewers returns the list of people who can see shared folders. -func Viewers(s domain.StoreContext) (v []Viewer, err error) { +func (s Scope) Viewers(ctx domain.RequestContext) (v []space.Viewer, err error) { sql := ` SELECT a.userid, COALESCE(u.firstname, '') as firstname, @@ -167,23 +173,23 @@ WHERE a.orgid=? AND b.type != 2 GROUP BY a.labelid,a.userid ORDER BY u.firstname,u.lastname` - err = s.Runtime.Db.Select(&v, sql, s.Context.OrgID) + err = s.Runtime.Db.Select(&v, sql, ctx.OrgID) return } // Delete removes space from the store. -func Delete(s domain.StoreContext, id string) (rows int64, err error) { +func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) { b := mysql.BaseQuery{} - return b.DeleteConstrained(s.Context.Transaction, "label", s.Context.OrgID, id) + return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id) } // AddRole inserts the given record into the labelrole database table. -func AddRole(s domain.StoreContext, r Role) (err error) { +func (s Scope) AddRole(ctx domain.RequestContext, r space.Role) (err error) { r.Created = time.Now().UTC() r.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.Preparex("INSERT INTO labelrole (refid, labelid, orgid, userid, canview, canedit, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + stmt, err := ctx.Transaction.Preparex("INSERT INTO labelrole (refid, labelid, orgid, userid, canview, canedit, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) if err != nil { @@ -201,10 +207,10 @@ func AddRole(s domain.StoreContext, r Role) (err error) { } // GetRoles returns a slice of labelrole records, for the given labelID in the client's organization, grouped by user. -func GetRoles(s domain.StoreContext, labelID string) (r []Role, err error) { +func (s Scope) GetRoles(ctx domain.RequestContext, labelID string) (r []space.Role, err error) { query := `SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND labelid=?` // was + "GROUP BY userid" - err = s.Runtime.Db.Select(&r, query, s.Context.OrgID, labelID) + err = s.Runtime.Db.Select(&r, query, ctx.OrgID, labelID) if err == sql.ErrNoRows { err = nil @@ -220,19 +226,19 @@ func GetRoles(s domain.StoreContext, labelID string) (r []Role, err error) { // GetUserRoles returns a slice of role records, for both the client's user and organization, and // those space roles that exist for all users in the client's organization. -func GetUserRoles(s domain.StoreContext) (r []Role, err error) { +func (s Scope) GetUserRoles(ctx domain.RequestContext) (r []space.Role, err error) { err = s.Runtime.Db.Select(&r, ` SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? and userid=? UNION ALL SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND userid=''`, - s.Context.OrgID, s.Context.UserID, s.Context.OrgID) + ctx.OrgID, ctx.UserID, ctx.OrgID) if err == sql.ErrNoRows { err = nil } if err != nil { - err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user space roles %s", s.Context.UserID)) + err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user space roles %s", ctx.UserID)) return } @@ -240,36 +246,36 @@ func GetUserRoles(s domain.StoreContext) (r []Role, err error) { } // DeleteRole deletes the labelRoleID record from the labelrole table. -func DeleteRole(s domain.StoreContext, roleID string) (rows int64, err error) { +func (s Scope) DeleteRole(ctx domain.RequestContext, roleID string) (rows int64, err error) { b := mysql.BaseQuery{} - sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND refid='%s'", s.Context.OrgID, roleID) + sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND refid='%s'", ctx.OrgID, roleID) - return b.DeleteWhere(s.Context.Transaction, sql) + return b.DeleteWhere(ctx.Transaction, sql) } // DeleteSpaceRoles deletes records from the labelrole table which have the given space ID. -func DeleteSpaceRoles(s domain.StoreContext, spaceID string) (rows int64, err error) { +func (s Scope) DeleteSpaceRoles(ctx domain.RequestContext, spaceID string) (rows int64, err error) { b := mysql.BaseQuery{} - sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s'", s.Context.OrgID, spaceID) + sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s'", ctx.OrgID, spaceID) - return b.DeleteWhere(s.Context.Transaction, sql) + return b.DeleteWhere(ctx.Transaction, sql) } // DeleteUserSpaceRoles removes all roles for the specified user, for the specified space. -func DeleteUserSpaceRoles(s domain.StoreContext, spaceID, userID string) (rows int64, err error) { +func (s Scope) DeleteUserSpaceRoles(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) { b := mysql.BaseQuery{} sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s' AND userid='%s'", - s.Context.OrgID, spaceID, userID) + ctx.OrgID, spaceID, userID) - return b.DeleteWhere(s.Context.Transaction, sql) + return b.DeleteWhere(ctx.Transaction, sql) } // MoveSpaceRoles changes the space ID for space role records from previousLabel to newLabel. -func MoveSpaceRoles(s domain.StoreContext, previousLabel, newLabel string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE labelrole SET labelid=? WHERE labelid=? AND orgid=?") +func (s Scope) MoveSpaceRoles(ctx domain.RequestContext, previousLabel, newLabel string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE labelrole SET labelid=? WHERE labelid=? AND orgid=?") defer streamutil.Close(stmt) if err != nil { @@ -277,7 +283,7 @@ func MoveSpaceRoles(s domain.StoreContext, previousLabel, newLabel string) (err return } - _, err = stmt.Exec(newLabel, previousLabel, s.Context.OrgID) + _, err = stmt.Exec(newLabel, previousLabel, ctx.OrgID) if err != nil { err = errors.Wrap(err, fmt.Sprintf("unable to execute move space roles for label %s", previousLabel)) } diff --git a/domain/space/permission.go b/domain/space/permission.go index 54a3a09d..6183a7a9 100644 --- a/domain/space/permission.go +++ b/domain/space/permission.go @@ -13,13 +13,7 @@ // Spaces in Documize contain documents. package space -import ( - "database/sql" - "fmt" - - "github.com/documize/community/domain" -) - +/* // CanViewSpace returns if the user has permission to view the given spaceID. func CanViewSpace(s domain.StoreContext, spaceID string) (hasPermission bool) { roles, err := GetRoles(s, spaceID) @@ -60,3 +54,4 @@ func CanViewSpaceDocuments(s domain.StoreContext, spaceID string) (hasPermission return false } +*/ diff --git a/domain/space/space.go b/domain/space/space.go index 830435f3..f6748c01 100644 --- a/domain/space/space.go +++ b/domain/space/space.go @@ -18,29 +18,30 @@ import ( "github.com/documize/community/core/secrets" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" - "github.com/documize/community/domain/account" - "github.com/documize/community/domain/user" + "github.com/documize/community/model/account" + "github.com/documize/community/model/space" + "github.com/documize/community/model/user" ) // addSpace prepares and creates space record. -func addSpace(s domain.StoreContext, sp Space) (err error) { - sp.Type = ScopePrivate - sp.UserID = s.Context.UserID +func addSpace(ctx domain.RequestContext, s *domain.Store, sp space.Space) (err error) { + sp.Type = space.ScopePrivate + sp.UserID = ctx.UserID - err = Add(s, sp) + err = s.Space.Add(ctx, sp) if err != nil { return } - role := Role{} + role := space.Role{} role.LabelID = sp.RefID role.OrgID = sp.OrgID - role.UserID = s.Context.UserID + role.UserID = ctx.UserID role.CanEdit = true role.CanView = true role.RefID = uniqueid.Generate() - err = AddRole(s, role) + err = s.Space.AddRole(ctx, role) return } @@ -49,8 +50,8 @@ func addSpace(s domain.StoreContext, sp Space) (err error) { // We create the user account with default values and then take them // through a welcome process designed to capture profile data. // We add them to the organization and grant them view-only folder access. -func inviteNewUserToSharedSpace(s domain.StoreContext, email string, invitedBy user.User, - baseURL string, sp Space, invitationMessage string) (err error) { +func inviteNewUserToSharedSpace(ctx domain.RequestContext, s *domain.Store, email string, invitedBy user.User, + baseURL string, sp space.Space, invitationMessage string) (err error) { var u = user.User{} u.Email = email @@ -62,7 +63,7 @@ func inviteNewUserToSharedSpace(s domain.StoreContext, email string, invitedBy u userID := uniqueid.Generate() u.RefID = userID - err = user.Add(s, u) + err = s.User.Add(ctx, u) if err != nil { return } @@ -70,28 +71,28 @@ func inviteNewUserToSharedSpace(s domain.StoreContext, email string, invitedBy u // Let's give this user access to the organization var a account.Account a.UserID = userID - a.OrgID = s.Context.OrgID + a.OrgID = ctx.OrgID a.Admin = false a.Editor = false a.Active = true accountID := uniqueid.Generate() a.RefID = accountID - err = account.Add(s, a) + err = s.Account.Add(ctx, a) if err != nil { return } - role := Role{} + role := space.Role{} role.LabelID = sp.RefID - role.OrgID = s.Context.OrgID + role.OrgID = ctx.OrgID role.UserID = userID role.CanEdit = false role.CanView = true roleID := uniqueid.Generate() role.RefID = roleID - err = AddRole(s, role) + err = s.Space.AddRole(ctx, role) if err != nil { return } diff --git a/domain/storer.go b/domain/storer.go new file mode 100644 index 00000000..83d6eeaa --- /dev/null +++ b/domain/storer.go @@ -0,0 +1,118 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +// Package domain ... +package domain + +import ( + "github.com/documize/community/model/account" + "github.com/documize/community/model/audit" + "github.com/documize/community/model/org" + "github.com/documize/community/model/pin" + "github.com/documize/community/model/space" + "github.com/documize/community/model/user" +) + +// Store provides access to data store (database) +type Store struct { + Space SpaceStorer + User UserStorer + Account AccountStorer + Organization OrganizationStorer + Pin PinStorer + Audit AuditStorer + Document DocumentStorer +} + +// SpaceStorer defines required methods for space management +type SpaceStorer interface { + Add(ctx RequestContext, sp space.Space) (err error) + Get(ctx RequestContext, id string) (sp space.Space, err error) + PublicSpaces(ctx RequestContext, orgID string) (sp []space.Space, err error) + GetAll(ctx RequestContext) (sp []space.Space, err error) + Update(ctx RequestContext, sp space.Space) (err error) + ChangeOwner(ctx RequestContext, currentOwner, newOwner string) (err error) + Viewers(ctx RequestContext) (v []space.Viewer, err error) + Delete(ctx RequestContext, id string) (rows int64, err error) + AddRole(ctx RequestContext, r space.Role) (err error) + GetRoles(ctx RequestContext, labelID string) (r []space.Role, err error) + GetUserRoles(ctx RequestContext) (r []space.Role, err error) + DeleteRole(ctx RequestContext, roleID string) (rows int64, err error) + DeleteSpaceRoles(ctx RequestContext, spaceID string) (rows int64, err error) + DeleteUserSpaceRoles(ctx RequestContext, spaceID, userID string) (rows int64, err error) + MoveSpaceRoles(ctx RequestContext, previousLabel, newLabel string) (err error) +} + +// UserStorer defines required methods for user management +type UserStorer interface { + Add(ctx RequestContext, u user.User) (err error) + Get(ctx RequestContext, id string) (u user.User, err error) + GetByDomain(ctx RequestContext, domain, email string) (u user.User, err error) + GetByEmail(ctx RequestContext, email string) (u user.User, err error) + GetByToken(ctx RequestContext, token string) (u user.User, err error) + GetBySerial(ctx RequestContext, serial string) (u user.User, err error) + GetActiveUsersForOrganization(ctx RequestContext) (u []user.User, err error) + GetUsersForOrganization(ctx RequestContext) (u []user.User, err error) + GetSpaceUsers(ctx RequestContext, folderID string) (u []user.User, err error) + UpdateUser(ctx RequestContext, u user.User) (err error) + UpdateUserPassword(ctx RequestContext, userID, salt, password string) (err error) + DeactiveUser(ctx RequestContext, userID string) (err error) + ForgotUserPassword(ctx RequestContext, email, token string) (err error) + CountActiveUsers(ctx RequestContext) (c int) +} + +// AccountStorer defines required methods for account management +type AccountStorer interface { + Add(ctx RequestContext, account account.Account) (err error) + GetUserAccount(ctx RequestContext, userID string) (account account.Account, err error) + GetUserAccounts(ctx RequestContext, userID string) (t []account.Account, err error) + GetAccountsByOrg(ctx RequestContext) (t []account.Account, err error) + DeleteAccount(ctx RequestContext, ID string) (rows int64, err error) + UpdateAccount(ctx RequestContext, account account.Account) (err error) + HasOrgAccount(ctx RequestContext, orgID, userID string) bool + CountOrgAccounts(ctx RequestContext) int +} + +// OrganizationStorer defines required methods for organization management +type OrganizationStorer interface { + AddOrganization(ctx RequestContext, org org.Organization) error + GetOrganization(ctx RequestContext, id string) (org org.Organization, err error) + GetOrganizationByDomain(ctx RequestContext, subdomain string) (org org.Organization, err error) + UpdateOrganization(ctx RequestContext, org org.Organization) (err error) + DeleteOrganization(ctx RequestContext, orgID string) (rows int64, err error) + RemoveOrganization(ctx RequestContext, orgID string) (err error) + UpdateAuthConfig(ctx RequestContext, org org.Organization) (err error) + CheckDomain(ctx RequestContext, domain string) string +} + +// PinStorer defines required methods for pin management +type PinStorer interface { + Add(ctx RequestContext, pin pin.Pin) (err error) + GetPin(ctx RequestContext, id string) (pin pin.Pin, err error) + GetUserPins(ctx RequestContext, userID string) (pins []pin.Pin, err error) + UpdatePin(ctx RequestContext, pin pin.Pin) (err error) + UpdatePinSequence(ctx RequestContext, pinID string, sequence int) (err error) + DeletePin(ctx RequestContext, id string) (rows int64, err error) + DeletePinnedSpace(ctx RequestContext, spaceID string) (rows int64, err error) + DeletePinnedDocument(ctx RequestContext, documentID string) (rows int64, err error) +} + +// AuditStorer defines required methods for audit trails +type AuditStorer interface { + Record(ctx RequestContext, t audit.EventType) +} + +// DocumentStorer defines required methods for document handling +type DocumentStorer interface { + MoveDocumentSpace(ctx RequestContext, id, move string) (err error) +} + +// https://github.com/golang-sql/sqlexp/blob/c2488a8be21d20d31abf0d05c2735efd2d09afe4/quoter.go#L46 diff --git a/domain/user/endpoint.go b/domain/user/endpoint.go new file mode 100644 index 00000000..fa30dbf3 --- /dev/null +++ b/domain/user/endpoint.go @@ -0,0 +1,616 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package user + +import ( + "github.com/documize/community/core/env" + "github.com/documize/community/domain" +) + +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store domain.Store +} + +/* +// AddUser is the endpoint that enables an administrator to add a new user for their orgaisation. +func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) { + method := "user.AddUser" + ctx := domain.GetRequestContext(r) + + if !h.Runtime.Product.License.IsValid() { + response.WriteBadLicense(w) + } + + if !s.Context.Administrator { + response.WriteForbiddenError(w) + return + } + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + response.WriteBadRequestError(w, method, err.Error()) + return + } + + userModel := model.User{} + err = json.Unmarshal(body, &userModel) + if err != nil { + response.WriteBadRequestError(w, method, err.Error()) + return + } + + // data validation + userModel.Email = strings.ToLower(strings.TrimSpace(userModel.Email)) + userModel.Firstname = strings.TrimSpace(userModel.Firstname) + userModel.Lastname = strings.TrimSpace(userModel.Lastname) + userModel.Password = strings.TrimSpace(userModel.Password) + + if len(userModel.Email) == 0 { + response.WriteMissingDataError(w, method, "email") + return + } + + if len(userModel.Firstname) == 0 { + response.WriteMissingDataError(w, method, "firsrtname") + return + } + + if len(userModel.Lastname) == 0 { + response.WriteMissingDataError(w, method, "lastname") + return + } + + userModel.Initials = stringutil.MakeInitials(userModel.Firstname, userModel.Lastname) + requestedPassword := secrets.GenerateRandomPassword() + userModel.Salt = secrets.GenerateSalt() + userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt) + + // only create account if not dupe + addUser := true + addAccount := true + var userID string + + userDupe, err := h.Store.User.GetByEmail(s, userModel.Email) + if err != nil && err != sql.ErrNoRows { + response.WriteServerError(w, method, err) + return + } + + if userModel.Email == userDupe.Email { + addUser = false + userID = userDupe.RefID + + h.Runtime.Log.Info("Dupe user found, will not add " + userModel.Email) + } + + s.Context.Transaction, err = request.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + if addUser { + userID = uniqueid.Generate() + userModel.RefID = userID + + err = h.Store.User.Add(s, userModel) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + h.Runtime.Log.Info("Adding user") + } else { + AttachUserAccounts(s, s.Context.OrgID, &userDupe) + + for _, a := range userDupe.Accounts { + if a.OrgID == s.Context.OrgID { + addAccount = false + h.Runtime.Log.Info("Dupe account found, will not add") + break + } + } + } + + // set up user account for the org + if addAccount { + var a model.Account + a.RefID = uniqueid.Generate() + a.UserID = userID + a.OrgID = s.Context.OrgID + a.Editor = true + a.Admin = false + a.Active = true + + err = account.Add(s, a) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + } + + if addUser { + event.Handler().Publish(string(event.TypeAddUser)) + eventing.Record(s, eventing.EventTypeUserAdd) + } + + if addAccount { + event.Handler().Publish(string(event.TypeAddAccount)) + eventing.Record(s, eventing.EventTypeAccountAdd) + } + + s.Context.Transaction.Commit() + + // If we did not add user or give them access (account) then we error back + if !addUser && !addAccount { + response.WriteDuplicateError(w, method, "user") + return + } + + // Invite new user + inviter, err := h.Store.User.Get(s, s.Context.UserID) + if err != nil { + response.WriteServerError(w, method, err) + return + } + + // Prepare invitation email (that contains SSO link) + if addUser && addAccount { + size := len(requestedPassword) + + auth := fmt.Sprintf("%s:%s:%s", s.Context.AppURL, userModel.Email, requestedPassword[:size]) + encrypted := secrets.EncodeBase64([]byte(auth)) + + url := fmt.Sprintf("%s/%s", s.Context.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted))) + go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword) + + h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, s.Context.AppURL)) + + } else { + go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), s.Context.GetAppURL("")) + + h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email)) + } + + response.WriteJSON(w, userModel) +} + +/* +// GetOrganizationUsers is the endpoint that allows administrators to view the users in their organisation. +func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) { + method := "pin.GetUserPins" + s := domain.NewContext(h.Runtime, r) + + if !s.Context.Editor && !s.Context.Administrator { + response.WriteForbiddenError(w) + return + } + + active, err := strconv.ParseBool(request.Query("active")) + if err != nil { + active = false + } + + u := []User{} + + if active { + u, err = GetActiveUsersForOrganization(s) + if err != nil && err != sql.ErrNoRows { + response.WriteServerError(w, method, err) + return + } + + } else { + u, err = GetUsersForOrganization(s) + if err != nil && err != sql.ErrNoRows { + response.WriteServerError(w, method, err) + return + } + } + + if len(u) == 0 { + u = []User{} + } + + for i := range u { + AttachUserAccounts(s, s.Context.OrgID, &u[i]) + } + + response.WriteJSON(w, u) +} + +// GetSpaceUsers returns every user within a given space +func (h *Handler) GetSpaceUsers(w http.ResponseWriter, r *http.Request) { + method := "user.GetSpaceUsers" + s := domain.NewContext(h.Runtime, r) + + var u []User + var err error + + folderID := request.Param("folderID") + if len(folderID) == 0 { + response.WriteMissingDataError(w, method, "folderID") + return + } + + // check to see space type as it determines user selection criteria + folder, err := space.Get(s, folderID) + if err != nil && err != sql.ErrNoRows { + h.Runtime.Log.Error("cannot get space", err) + response.WriteJSON(w, u) + return + } + + switch folder.Type { + case entity.FolderTypePublic: + u, err = GetActiveUsersForOrganization(s) + break + case entity.FolderTypePrivate: + // just me + var me User + user, err = Get(s, s.Context.UserID) + u = append(u, me) + break + case entity.FolderTypeRestricted: + u, err = GetSpaceUsers(s, folderID) + break + } + + if len(u) == 0 { + u = []User + } + + if err != nil && err != sql.ErrNoRows { + h.Runtime.Log.Error("cannot get users for space", err) + response.WriteJSON(w, u) + return + } + + response.WriteJSON(w, u) +} + +// GetUser returns user specified by ID +func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { + method := "user.GetUser" + s := domain.NewContext(h.Runtime, r) + + userID := request.Param("userID") + if len(userID) == 0 { + response.WriteMissingDataError(w, method, "userId") + return + } + + if userID != s.Context.UserID { + response.WriteBadRequestError(w, method, "userId mismatch") + return + } + + u, err := GetSecuredUser(s, s.Context.OrgID, userID) + if err == sql.ErrNoRows { + response.WriteNotFoundError(s, method, s.Context.UserID) + return + } + if err != nil { + response.WriteServerError(w, method, err) + return + } + + response.WriteJSON(u) + +// DeleteUser is the endpoint to delete a user specified by userID, the caller must be an Administrator. +func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) { + method := "user.DeleteUser" + s := domain.NewContext(h.Runtime, r) + + if !s.Context.Administrator { + response.WriteForbiddenError(w) + return + } + + userID := response.Params("userID") + if len(userID) == 0 { + response.WriteMissingDataError(w, method, "userID") + return + } + + if userID == s.Context.UserID { + response.WriteBadRequestError(w, method, "cannot delete self") + return + } + + var err error + s.Context.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + err = DeactiveUser(s, userID) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + err = space.ChangeLabelOwner(s, userID, s.Context.UserID) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + eventing.Record(s, eventing.EventTypeUserDelete) + event.Handler().Publish(string(event.TypeRemoveUser)) + + s.Context.Transaction.Commit() + + response.WriteEmpty() +} + +// UpdateUser is the endpoint to update user information for the given userID. +// Note that unless they have admin privildges, a user can only update their own information. +// Also, only admins can update user roles in organisations. +func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) { + method := "user.DeleteUser" + s := domain.NewContext(h.Runtime, r) + + userID := request.Param("userID") + if len(userID) == 0 { + response.WriteBadRequestError(w, method, "user id must be numeric") + return + } + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + response.WritePayloadError(w, method, err) + return + } + + u := User{} + err = json.Unmarshal(body, &u) + if err != nil { + response.WriteBadRequestError(w, method, err.Error()) + return + } + + // can only update your own account unless you are an admin + if s.Context.UserID != userID && !s.Context.Administrator { + response.WriteForbiddenError(w) + return + } + + // can only update your own account unless you are an admin + if len(u.Email) == 0 { + response.WriteMissingDataError(w, method, "email") + return + } + + s.Context.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + u.RefID = userID + u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname) + + err = UpdateUser(s, u) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + // Now we update user roles for this organization. + // That means we have to first find their account record + // for this organization. + a, err := account.GetUserAccount(s, userID) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + a.Editor = u.Editor + a.Admin = u.Admin + a.Active = u.Active + + err = account.UpdateAccount(s, account) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + eventing.Record(s, eventing.EventTypeUserUpdate) + + s.Context.Transaction.Commit() + + response.WriteJSON(u) +} + +// ChangeUserPassword accepts password change from within the app. +func (h *Handler) ChangeUserPassword(w http.ResponseWriter, r *http.Request) { + method := "user.ChangeUserPassword" + s := domain.NewContext(h.Runtime, r) + + userID := response.Param("userID") + if len(userID) == 0 { + response.WriteMissingDataError(w, method, "user id") + return + } + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + response.WriteBadRequestError(w, method, err.Error()) + return + } + newPassword := string(body) + + // can only update your own account unless you are an admin + if userID != s.Context.UserID && !s.Context.Administrator { + response.WriteForbiddenError(w) + return + } + + s.Context.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + u, err := Get(s, userID) + if err != nil { + response.WriteServerError(w, method, err) + return + } + + u.Salt = secrets.GenerateSalt() + + err = UpdateUserPassword(s, userID, user.Salt, secrets.GeneratePassword(newPassword, user.Salt)) + if err != nil { + response.WriteServerError(w, method, err) + return + } + + s.Context.Transaction.Rollback() + response.WriteEmpty(w) +} + +// GetUserFolderPermissions returns folder permission for authenticated user. +func (h *Handler) GetUserFolderPermissions(w http.ResponseWriter, r *http.Request) { + method := "user.ChangeUserPassword" + s := domain.NewContext(h.Runtime, r) + + userID := request.Param("userID") + if userID != p.Context.UserID { + response.WriteForbiddenError(w) + return + } + + roles, err := space.GetUserLabelRoles(s, userID) + if err == sql.ErrNoRows { + err = nil + roles = []space.Role{} + } + if err != nil { + response.WriteServerError(w, method, err) + return + } + + response.WriteJSON(w, roles) +} + +// ForgotUserPassword initiates the change password procedure. +// Generates a reset token and sends email to the user. +// User has to click link in email and then provide a new password. +func (h *Handler) ForgotUserPassword(w http.ResponseWriter, r *http.Request) { + method := "user.ForgotUserPassword" + s := domain.NewContext(h.Runtime, r) + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + response.WriteBadRequestError(w, method, "cannot ready payload") + return + } + + u := new(User) + err = json.Unmarshal(body, &u) + if err != nil { + response.WriteBadRequestError(w, method, "JSON body") + return + } + + s.Context.Transaction, err = request.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + token := secrets.GenerateSalt() + + err = ForgotUserPassword(s, u.Email, token) + if err != nil && err != sql.ErrNoRows { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + if err == sql.ErrNoRows { + response.WriteEmpty(w) + h.Runtime.Log.Info(fmt.Errorf("User %s not found for password reset process", u.Email)) + return + } + + s.Context.Transaction.Commit() + + appURL := s.Context.GetAppURL(fmt.Sprintf("auth/reset/%s", token)) + go mail.PasswordReset(u.Email, appURL) + + response.WriteEmpty(w) +} + +// ResetUserPassword stores the newly chosen password for the user. +func (h *Handler) ResetUserPassword(w http.ResponseWriter, r *http.Request) { + method := "user.ForgotUserPassword" + s := domain.NewContext(h.Runtime, r) + + token := request.Param("token") + if len(token) == 0 { + response.WriteMissingDataError(w, method, "missing token") + return + } + + defer streamutil.Close(r.Body) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + response.WriteBadRequestError(w, method, "JSON body") + return + } + newPassword := string(body) + + s.Context.Transaction, err = h.Runtime.Db.Beginx() + if err != nil { + response.WriteServerError(w, method, err) + return + } + + u, err := GetByToken(token) + if err != nil { + response.WriteServerError(w, method, err) + return + } + + user.Salt = secrets.GenerateSalt() + + err = UpdateUserPassword(s, u.RefID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt)) + if err != nil { + s.Context.Transaction.Rollback() + response.WriteServerError(w, method, err) + return + } + + eventing.Record(s, eventing.EventTypeUserPasswordReset) + + s.Context.Transaction.Commit() + + response.WriteEmpty(w) +} +*/ diff --git a/domain/user/store.go b/domain/user/mysql/store.go similarity index 76% rename from domain/user/store.go rename to domain/user/mysql/store.go index d6a59b1b..61df90bd 100644 --- a/domain/user/store.go +++ b/domain/user/mysql/store.go @@ -9,7 +9,7 @@ // // https://documize.com -package user +package mysql import ( "database/sql" @@ -17,17 +17,24 @@ import ( "strings" "time" + "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/domain" + "github.com/documize/community/model/user" "github.com/pkg/errors" ) +// Scope provides data access to MySQL. +type Scope struct { + Runtime *env.Runtime +} + // Add adds the given user record to the user table. -func Add(s domain.StoreContext, u User) (err error) { +func (s Scope) Add(ctx domain.RequestContext, u user.User) (err error) { u.Created = time.Now().UTC() u.Revised = time.Now().UTC() - stmt, err := s.Context.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + stmt, err := ctx.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") defer streamutil.Close(stmt) if err != nil { @@ -45,7 +52,7 @@ func Add(s domain.StoreContext, u User) (err error) { } // Get returns the user record for the given id. -func Get(s domain.StoreContext, id string) (u User, err error) { +func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?") defer streamutil.Close(stmt) @@ -64,7 +71,7 @@ func Get(s domain.StoreContext, id string) (u User, err error) { } // GetByDomain matches user by email and domain. -func GetByDomain(s domain.StoreContext, domain, email string) (u User, err error) { +func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u user.User, err error) { email = strings.TrimSpace(strings.ToLower(email)) stmt, err := s.Runtime.Db.Preparex("SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?") @@ -85,7 +92,7 @@ func GetByDomain(s domain.StoreContext, domain, email string) (u User, err error } // GetByEmail returns a single row match on email. -func GetByEmail(s domain.StoreContext, email string) (u User, err error) { +func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User, err error) { email = strings.TrimSpace(strings.ToLower(email)) stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?") @@ -106,7 +113,7 @@ func GetByEmail(s domain.StoreContext, email string) (u User, err error) { } // GetByToken returns a user record given a reset token value. -func GetByToken(s domain.StoreContext, token string) (u User, err error) { +func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?") defer streamutil.Close(stmt) @@ -127,7 +134,7 @@ func GetByToken(s domain.StoreContext, token string) (u User, err error) { // GetBySerial is used to retrieve a user via their temporary password salt value! // This occurs when we you share a folder with a new user and they have to complete // the onboarding process. -func GetBySerial(s domain.StoreContext, serial string) (u User, err error) { +func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.User, err error) { stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?") defer streamutil.Close(stmt) @@ -147,15 +154,15 @@ func GetBySerial(s domain.StoreContext, serial string) (u User, err error) { // GetActiveUsersForOrganization returns a slice containing of active user records for the organization // identified in the Persister. -func GetActiveUsersForOrganization(s domain.StoreContext) (u []User, err error) { +func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) { err = s.Runtime.Db.Select(&u, `SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised FROM user u WHERE u.refid IN (SELECT userid FROM account WHERE orgid = ? AND active=1) ORDER BY u.firstname,u.lastname`, - s.Context.OrgID) + ctx.OrgID) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", s.Context.OrgID)) + err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID)) return } @@ -164,12 +171,12 @@ func GetActiveUsersForOrganization(s domain.StoreContext) (u []User, err error) // GetUsersForOrganization returns a slice containing all of the user records for the organizaiton // identified in the Persister. -func GetUsersForOrganization(s domain.StoreContext) (u []User, err error) { +func (s Scope) GetUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) { err = s.Runtime.Db.Select(&u, - "SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised FROM user WHERE refid IN (SELECT userid FROM account where orgid = ?) ORDER BY firstname,lastname", s.Context.OrgID) + "SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised FROM user WHERE refid IN (SELECT userid FROM account where orgid = ?) ORDER BY firstname,lastname", ctx.OrgID) if err != nil { - err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", s.Context.OrgID)) + err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID)) return } @@ -177,17 +184,17 @@ func GetUsersForOrganization(s domain.StoreContext) (u []User, err error) { } // GetSpaceUsers returns a slice containing all user records for given folder. -func GetSpaceUsers(s domain.StoreContext, folderID string) (u []User, err error) { +func (s Scope) GetSpaceUsers(ctx domain.RequestContext, folderID string) (u []user.User, err error) { err = s.Runtime.Db.Select(&u, - `SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised + `SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised FROM user u, account a - WHERE u.refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) + WHERE u.refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) AND a.orgid=? AND u.refid = a.userid AND a.active=1 ORDER BY u.firstname, u.lastname`, - s.Context.OrgID, folderID, s.Context.OrgID) + ctx.OrgID, folderID, ctx.OrgID) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", s.Context.OrgID)) + err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", ctx.OrgID)) return } @@ -195,11 +202,11 @@ func GetSpaceUsers(s domain.StoreContext, folderID string) (u []User, err error) } // UpdateUser updates the user table using the given replacement user record. -func UpdateUser(s domain.StoreContext, u User) (err error) { +func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) { u.Revised = time.Now().UTC() u.Email = strings.ToLower(u.Email) - stmt, err := s.Context.Transaction.PrepareNamed( + stmt, err := ctx.Transaction.PrepareNamed( "UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid") defer streamutil.Close(stmt) @@ -218,8 +225,8 @@ func UpdateUser(s domain.StoreContext, u User) (err error) { } // UpdateUserPassword updates a user record with new password and salt values. -func UpdateUserPassword(s domain.StoreContext, userID, salt, password string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?") +func (s Scope) UpdateUserPassword(ctx domain.RequestContext, userID, salt, password string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?") defer streamutil.Close(stmt) if err != nil { @@ -237,8 +244,8 @@ func UpdateUserPassword(s domain.StoreContext, userID, salt, password string) (e } // DeactiveUser deletes the account record for the given userID and persister.Context.OrgID. -func DeactiveUser(s domain.StoreContext, userID string) (err error) { - stmt, err := s.Context.Transaction.Preparex("DELETE FROM account WHERE userid=? and orgid=?") +func (s Scope) DeactiveUser(ctx domain.RequestContext, userID string) (err error) { + stmt, err := ctx.Transaction.Preparex("DELETE FROM account WHERE userid=? and orgid=?") defer streamutil.Close(stmt) if err != nil { @@ -246,7 +253,7 @@ func DeactiveUser(s domain.StoreContext, userID string) (err error) { return } - _, err = stmt.Exec(userID, s.Context.OrgID) + _, err = stmt.Exec(userID, ctx.OrgID) if err != nil { err = errors.Wrap(err, "execute user deactivation") @@ -257,8 +264,8 @@ func DeactiveUser(s domain.StoreContext, userID string) (err error) { } // ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email. -func ForgotUserPassword(s domain.StoreContext, email, token string) (err error) { - stmt, err := s.Context.Transaction.Preparex("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?") +func (s Scope) ForgotUserPassword(ctx domain.RequestContext, email, token string) (err error) { + stmt, err := ctx.Transaction.Preparex("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?") defer streamutil.Close(stmt) if err != nil { @@ -276,7 +283,7 @@ func ForgotUserPassword(s domain.StoreContext, email, token string) (err error) } // CountActiveUsers returns the number of active users in the system. -func CountActiveUsers(s domain.StoreContext) (c int) { +func (s Scope) CountActiveUsers(ctx domain.RequestContext) (c int) { row := s.Runtime.Db.QueryRow("SELECT count(*) FROM user u WHERE u.refid IN (SELECT userid FROM account WHERE active=1)") err := row.Scan(&c) diff --git a/domain/user/user.go b/domain/user/user.go index 7e1780fe..ab7808d6 100644 --- a/domain/user/user.go +++ b/domain/user/user.go @@ -13,23 +13,23 @@ package user import ( "github.com/documize/community/domain" - "github.com/documize/community/domain/account" + "github.com/documize/community/model/user" "github.com/pkg/errors" ) // GetSecuredUser contain associated accounts but credentials are wiped. -func GetSecuredUser(s domain.StoreContext, orgID, q string) (u User, err error) { - u, err = Get(s, q) - AttachUserAccounts(s, orgID, &u) +func GetSecuredUser(ctx domain.RequestContext, s domain.Store, orgID, q string) (u user.User, err error) { + u, err = s.User.Get(ctx, q) + AttachUserAccounts(ctx, s, orgID, &u) return } // AttachUserAccounts attachs user accounts to user object. -func AttachUserAccounts(s domain.StoreContext, orgID string, u *User) { +func AttachUserAccounts(ctx domain.RequestContext, s domain.Store, orgID string, u *user.User) { u.ProtectSecrets() - a, err := account.GetUserAccounts(s, u.RefID) + a, err := s.Account.GetUserAccounts(ctx, u.RefID) if err != nil { err = errors.Wrap(err, "fetch user accounts") return diff --git a/edition/boot/runtime.go b/edition/boot/runtime.go index bfdffb37..0e44377b 100644 --- a/edition/boot/runtime.go +++ b/edition/boot/runtime.go @@ -19,11 +19,12 @@ import ( "github.com/documize/community/core/database" "github.com/documize/community/core/env" "github.com/documize/community/core/secrets" + "github.com/documize/community/domain" "github.com/jmoiron/sqlx" ) // InitRuntime prepares runtime using command line and environment variables. -func InitRuntime(r *env.Runtime) bool { +func InitRuntime(r *env.Runtime, s *domain.Store) bool { // We need SALT to hash auth JWT tokens if r.Flags.Salt == "" { r.Flags.Salt = secrets.RandSalt() @@ -76,6 +77,9 @@ func InitRuntime(r *env.Runtime) bool { } } + // setup store based upon database type + AttachStore(r, s) + return true } diff --git a/edition/boot/store.go b/edition/boot/store.go new file mode 100644 index 00000000..60c209b2 --- /dev/null +++ b/edition/boot/store.go @@ -0,0 +1,36 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +// Package boot prepares runtime environment. +package boot + +import ( + "github.com/documize/community/core/env" + "github.com/documize/community/domain" + account "github.com/documize/community/domain/account/mysql" + audit "github.com/documize/community/domain/audit/mysql" + org "github.com/documize/community/domain/organization/mysql" + pin "github.com/documize/community/domain/pin/mysql" + space "github.com/documize/community/domain/space/mysql" + user "github.com/documize/community/domain/user/mysql" + doc "github.com/documize/community/domain/document/mysql" +) + +// AttachStore selects database persistence layer +func AttachStore(r *env.Runtime, s *domain.Store) { + s.Space = space.Scope{Runtime: r} + s.Account = account.Scope{Runtime: r} + s.Organization = org.Scope{Runtime: r} + s.User = user.Scope{Runtime: r} + s.Pin = pin.Scope{Runtime: r} + s.Audit = audit.Scope{Runtime: r} + s.Document = doc.Scope{Runtime: r} +} diff --git a/edition/community.go b/edition/community.go index bed489d4..df872531 100644 --- a/edition/community.go +++ b/edition/community.go @@ -24,6 +24,7 @@ import ( _ "github.com/documize/community/embed" // the compressed front-end code and static data "github.com/documize/community/server" _ "github.com/go-sql-driver/mysql" // the mysql driver is required behind the scenes + "github.com/documize/community/domain" ) var rt env.Runtime @@ -49,9 +50,12 @@ func main() { rt.Product.License.Trial = false rt.Product.License.Edition = "Community" + // setup store + s := domain.Store{} + // parse settings from command line and environment rt.Flags = env.ParseFlags() - flagsOK := boot.InitRuntime(&rt) + flagsOK := boot.InitRuntime(&rt, &s) if flagsOK { // runtime.Log = runtime.Log.SetDB(runtime.Db) @@ -65,5 +69,5 @@ func main() { section.Register(rt) ready := make(chan struct{}, 1) // channel signals router ready - server.Start(&rt, ready) + server.Start(&rt, &s, ready) } diff --git a/domain/account/model.go b/model/account/account.go similarity index 76% rename from domain/account/model.go rename to model/account/account.go index bbbc9804..e4416d11 100644 --- a/domain/account/model.go +++ b/model/account/account.go @@ -11,19 +11,11 @@ package account -import ( - "github.com/documize/community/core/env" - "github.com/documize/community/domain" -) - -// Handler contains the runtime information such as logging and database. -type Handler struct { - Runtime *env.Runtime -} +import "github.com/documize/community/model" // Account links a User to an Organization. type Account struct { - domain.BaseEntity + model.BaseEntity Admin bool `json:"admin"` Editor bool `json:"editor"` UserID string `json:"userId"` diff --git a/domain/eventing/model.go b/model/audit/audit.go similarity index 99% rename from domain/eventing/model.go rename to model/audit/audit.go index 5f2b79cc..2da4039f 100644 --- a/domain/eventing/model.go +++ b/model/audit/audit.go @@ -10,7 +10,7 @@ // https://documize.com // Package eventing records and propagates events based on user actions. -package eventing +package audit import "time" diff --git a/domain/model.go b/model/base.go similarity index 96% rename from domain/model.go rename to model/base.go index 62fcb542..7860d82a 100644 --- a/domain/model.go +++ b/model/base.go @@ -9,8 +9,8 @@ // // https://documize.com -// Package domain ... -package domain +// Package model ... +package model import ( "time" diff --git a/domain/organization/model.go b/model/org/org.go similarity index 80% rename from domain/organization/model.go rename to model/org/org.go index 5fe0a1ea..8849de6f 100644 --- a/domain/organization/model.go +++ b/model/org/org.go @@ -9,21 +9,13 @@ // // https://documize.com -package organization +package org -import ( - "github.com/documize/community/core/env" - "github.com/documize/community/domain" -) - -// Handler contains the runtime information such as logging and database. -type Handler struct { - Runtime *env.Runtime -} +import "github.com/documize/community/model" // Organization defines a company that uses this app. type Organization struct { - domain.BaseEntity + model.BaseEntity Company string `json:"-"` Title string `json:"title"` Message string `json:"message"` diff --git a/domain/pin/model.go b/model/pin/pin.go similarity index 75% rename from domain/pin/model.go rename to model/pin/pin.go index a93f4d29..f8858d2e 100644 --- a/domain/pin/model.go +++ b/model/pin/pin.go @@ -11,19 +11,11 @@ package pin -import ( - "github.com/documize/community/core/env" - "github.com/documize/community/domain" -) - -// Handler contains the runtime information such as logging and database. -type Handler struct { - Runtime *env.Runtime -} +import "github.com/documize/community/model" // Pin defines a saved link to a document or space type Pin struct { - domain.BaseEntity + model.BaseEntity OrgID string `json:"orgId"` UserID string `json:"userId"` FolderID string `json:"folderId"` diff --git a/domain/space/model.go b/model/space/space.go similarity index 86% rename from domain/space/model.go rename to model/space/space.go index 2d0cdda0..a141dc7e 100644 --- a/domain/space/model.go +++ b/model/space/space.go @@ -9,23 +9,13 @@ // // https://documize.com -// Package space handles API calls and persistence for spaces. -// Spaces in Documize contain documents. package space -import ( - "github.com/documize/community/core/env" - "github.com/documize/community/domain" -) - -// Handler contains the runtime information such as logging and database. -type Handler struct { - Runtime *env.Runtime -} +import "github.com/documize/community/model" // Space defines a container for documents. type Space struct { - domain.BaseEntity + model.BaseEntity Name string `json:"name"` OrgID string `json:"orgId"` UserID string `json:"userId"` @@ -63,7 +53,7 @@ func (l *Space) IsRestricted() bool { // Role determines user permissions for a folder. type Role struct { - domain.BaseEntityObfuscated + model.BaseEntityObfuscated OrgID string `json:"-"` LabelID string `json:"folderId"` UserID string `json:"userId"` diff --git a/domain/user/model.go b/model/user/user.go similarity index 84% rename from domain/user/model.go rename to model/user/user.go index 2aba0749..5776b475 100644 --- a/domain/user/model.go +++ b/model/user/user.go @@ -14,19 +14,13 @@ package user import ( "fmt" - "github.com/documize/community/core/env" - "github.com/documize/community/domain" - "github.com/documize/community/domain/account" + "github.com/documize/community/model" + "github.com/documize/community/model/account" ) -// Handler contains the runtime information such as logging and database. -type Handler struct { - Runtime *env.Runtime -} - // User defines a login. type User struct { - domain.BaseEntity + model.BaseEntity Firstname string `json:"firstname"` Lastname string `json:"lastname"` Email string `json:"email"` diff --git a/server/middleware.go b/server/middleware.go index fc06177d..dfdb85dd 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -25,10 +25,12 @@ import ( "github.com/documize/community/domain/auth" "github.com/documize/community/domain/organization" "github.com/documize/community/domain/user" + "github.com/documize/community/model/org" ) type middleware struct { Runtime *env.Runtime + Store *domain.Store } func (m *middleware) cors(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { @@ -65,8 +67,6 @@ func (m *middleware) metrics(w http.ResponseWriter, r *http.Request, next http.H func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { method := "Authorize" - s := domain.StoreContext{Runtime: m.Runtime, Context: domain.RequestContext{}} - // Let certain requests pass straight through authenticated := preAuthorizeStaticAssets(m.Runtime, r) @@ -74,13 +74,15 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http token := auth.FindJWT(r) rc, _, tokenErr := auth.DecodeJWT(m.Runtime, token) - var org = organization.Organization{} + var org = org.Organization{} var err = errors.New("") if len(rc.OrgID) == 0 { - org, err = organization.GetOrganizationByDomain(s, organization.GetRequestSubdomain(s, r)) + dom := organization.GetRequestSubdomain(r) + dom = m.Store.Organization.CheckDomain(rc, dom) + org, err = m.Store.Organization.GetOrganizationByDomain(rc, dom) } else { - org, err = organization.GetOrganization(s, rc.OrgID) + org, err = m.Store.Organization.GetOrganization(rc, rc.OrgID) } // Inability to find org record spells the end of this request. @@ -96,8 +98,8 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http } rc.Subdomain = org.Domain - dom := organization.GetSubdomainFromHost(s, r) - dom2 := organization.GetRequestSubdomain(s, r) + dom := organization.GetSubdomainFromHost(r) + dom2 := organization.GetRequestSubdomain(r) if org.Domain != dom && org.Domain != dom2 { m.Runtime.Log.Info(fmt.Sprintf("domain mismatch %s vs. %s vs. %s", dom, dom2, org.Domain)) @@ -130,7 +132,7 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http rc.Editor = false rc.Global = false rc.AppURL = r.Host - rc.Subdomain = organization.GetSubdomainFromHost(s, r) + rc.Subdomain = organization.GetSubdomainFromHost(r) rc.SSL = r.TLS != nil // get user IP from request @@ -148,8 +150,7 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http // Fetch user permissions for this org if rc.Authenticated { - u, err := user.GetSecuredUser(s, org.RefID, rc.UserID) - + u, err := user.GetSecuredUser(rc, *m.Store, org.RefID, rc.UserID) if err != nil { response.WriteServerError(w, method, err) return diff --git a/server/routing/entries.go b/server/routing/entries.go index a7cda3f2..0da266be 100644 --- a/server/routing/entries.go +++ b/server/routing/entries.go @@ -17,11 +17,15 @@ import ( "github.com/documize/community/core/api" "github.com/documize/community/core/api/endpoint" "github.com/documize/community/core/env" + "github.com/documize/community/domain" + "github.com/documize/community/domain/organization" + "github.com/documize/community/domain/pin" + "github.com/documize/community/domain/space" "github.com/documize/community/server/web" ) // RegisterEndpoints register routes for serving API endpoints -func RegisterEndpoints(rt *env.Runtime) { +func RegisterEndpoints(rt *env.Runtime, s *domain.Store) { //************************************************** // Non-secure routes //************************************************** @@ -75,20 +79,22 @@ func RegisterEndpoints(rt *env.Runtime) { Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/copy/{targetID}", []string{"POST", "OPTIONS"}, nil, endpoint.CopyPage) // Organization - Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, endpoint.GetOrganization) - Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"PUT", "OPTIONS"}, nil, endpoint.UpdateOrganization) + organization := organization.Handler{Runtime: rt, Store: s} + Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, organization.Get) + Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update) - // Folder - Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"DELETE", "OPTIONS"}, nil, endpoint.DeleteFolder) - Add(rt, RoutePrefixPrivate, "folders/{folderID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, endpoint.RemoveFolder) - Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"PUT", "OPTIONS"}, nil, endpoint.SetFolderPermissions) - Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"GET", "OPTIONS"}, nil, endpoint.GetFolderPermissions) - Add(rt, RoutePrefixPrivate, "folders/{folderID}/invitation", []string{"POST", "OPTIONS"}, nil, endpoint.InviteToFolder) - Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, endpoint.GetFolderVisibility) - Add(rt, RoutePrefixPrivate, "folders", []string{"POST", "OPTIONS"}, nil, endpoint.AddFolder) - Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, nil, endpoint.GetFolders) - Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"GET", "OPTIONS"}, nil, endpoint.GetFolder) - Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"PUT", "OPTIONS"}, nil, endpoint.UpdateFolder) + // Space + space := space.Handler{Runtime: rt, Store: s} + Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete) + Add(rt, RoutePrefixPrivate, "folders/{folderID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove) + Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"PUT", "OPTIONS"}, nil, space.SetPermissions) + Add(rt, RoutePrefixPrivate, "folders/{folderID}/permissions", []string{"GET", "OPTIONS"}, nil, space.GetPermissions) + Add(rt, RoutePrefixPrivate, "folders/{folderID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite) + Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, []string{"filter", "viewers"}, space.GetSpaceViewers) + Add(rt, RoutePrefixPrivate, "folders", []string{"POST", "OPTIONS"}, nil, space.Add) + Add(rt, RoutePrefixPrivate, "folders", []string{"GET", "OPTIONS"}, nil, space.GetAll) + Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"GET", "OPTIONS"}, nil, space.Get) + Add(rt, RoutePrefixPrivate, "folders/{folderID}", []string{"PUT", "OPTIONS"}, nil, space.Update) // Users Add(rt, RoutePrefixPrivate, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, endpoint.ChangeUserPassword) @@ -136,10 +142,11 @@ func RegisterEndpoints(rt *env.Runtime) { Add(rt, RoutePrefixPrivate, "global/auth", []string{"PUT", "OPTIONS"}, nil, endpoint.SaveAuthConfig) // Pinned items - Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, endpoint.AddPin) - Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, endpoint.GetUserPins) - Add(rt, RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, endpoint.UpdatePinSequence) - Add(rt, RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, endpoint.DeleteUserPin) + pin := pin.Handler{Runtime: rt, Store: s} + Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, pin.Add) + Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, pin.GetUserPins) + Add(rt, RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, pin.UpdatePinSequence) + Add(rt, RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, pin.DeleteUserPin) // Single page app handler Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, endpoint.GetRobots) diff --git a/server/server.go b/server/server.go index 7a9cde75..793350e9 100644 --- a/server/server.go +++ b/server/server.go @@ -22,6 +22,7 @@ import ( "github.com/documize/community/core/api/plugins" "github.com/documize/community/core/database" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/server/routing" "github.com/documize/community/server/web" "github.com/gorilla/mux" @@ -30,8 +31,7 @@ import ( var testHost string // used during automated testing // Start router to handle all HTTP traffic. -func Start(rt *env.Runtime, ready chan struct{}) { - +func Start(rt *env.Runtime, s *domain.Store, ready chan struct{}) { err := plugins.LibSetup() if err != nil { rt.Log.Error("Terminating before running - invalid plugin.json", err) @@ -54,10 +54,10 @@ func Start(rt *env.Runtime, ready chan struct{}) { } // define middleware - cm := middleware{Runtime: rt} + cm := middleware{Runtime: rt, Store: s} // define API endpoints - routing.RegisterEndpoints(rt) + routing.RegisterEndpoints(rt, s) // wire up API endpoints router := mux.NewRouter()