mirror of
https://github.com/documize/community.git
synced 2025-08-04 21:15:24 +02:00
still moving codebase to new API (WIP)
This commit is contained in:
parent
72b14def6d
commit
d90b3249c3
44 changed files with 5276 additions and 336 deletions
224
domain/attachment/endpoint.go
Normal file
224
domain/attachment/endpoint.go
Normal file
|
@ -0,0 +1,224 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package attachment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"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/secrets"
|
||||
"github.com/documize/community/core/uniqueid"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/document"
|
||||
"github.com/documize/community/model/attachment"
|
||||
"github.com/documize/community/model/audit"
|
||||
uuid "github.com/nu7hatch/gouuid"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *domain.Store
|
||||
}
|
||||
|
||||
// Download is the end-point that responds to a request for a particular attachment
|
||||
// by sending the requested file to the client.
|
||||
func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||
method := "attachment.Download"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
a, err := h.Store.Attachment.GetAttachment(ctx, request.Param(r, "orgID"), request.Param(r, "attachmentID"))
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(w, method, request.Param(r, "fileID"))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
typ := mime.TypeByExtension("." + a.Extension)
|
||||
if typ == "" {
|
||||
typ = "application/octet-stream"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", typ)
|
||||
w.Header().Set("Content-Disposition", `Attachment; filename="`+a.Filename+`" ; `+`filename*="`+a.Filename+`"`)
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(a.Data)))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = w.Write(a.Data)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("writing attachment", err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentDownload)
|
||||
}
|
||||
|
||||
// Get is an end-point that returns all of the attachments of a particular documentID.
|
||||
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
method := "attachment.GetAttachments"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !document.CanViewDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
a, err := h.Store.Attachment.GetAttachments(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
a = []attachment.Attachment{}
|
||||
}
|
||||
|
||||
response.WriteJSON(w, a)
|
||||
}
|
||||
|
||||
// Delete is an endpoint that deletes a particular document attachment.
|
||||
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
method := "attachment.DeleteAttachment"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
attachmentID := request.Param(r, "attachmentID")
|
||||
if len(attachmentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "attachmentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.Store.Attachment.Delete(ctx, attachmentID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Mark references to this document as orphaned
|
||||
err = h.Store.Link.MarkOrphanAttachmentLink(ctx, attachmentID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentDelete)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// Add stores files against a document.
|
||||
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||
method := "attachment.Add"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
filedata, filename, err := r.FormFile("attachment")
|
||||
|
||||
if err != nil {
|
||||
response.WriteMissingDataError(w, method, "attachment")
|
||||
return
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
_, err = io.Copy(b, filedata)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var job = "some-uuid"
|
||||
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
job = newUUID.String()
|
||||
|
||||
var a attachment.Attachment
|
||||
refID := uniqueid.Generate()
|
||||
a.RefID = refID
|
||||
a.DocumentID = documentID
|
||||
a.Job = job
|
||||
random := secrets.GenerateSalt()
|
||||
a.FileID = random[0:9]
|
||||
a.Filename = filename.Filename
|
||||
a.Data = b.Bytes()
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Store.Attachment.Add(ctx, a)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentAdd)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
104
domain/attachment/mysql/store.go
Normal file
104
domain/attachment/mysql/store.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package attachment
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store/mysql"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/streamutil"
|
||||
"github.com/documize/community/model/attachment"
|
||||
)
|
||||
|
||||
// Scope provides data access to MySQL.
|
||||
type Scope struct {
|
||||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
// Add inserts the given record into the database attachement table.
|
||||
func (s Scope) Add(ctx domain.RequestContext, a attachment.Attachment) (err error) {
|
||||
a.OrgID = ctx.OrgID
|
||||
a.Created = time.Now().UTC()
|
||||
a.Revised = time.Now().UTC()
|
||||
bits := strings.Split(a.Filename, ".")
|
||||
a.Extension = bits[len(bits)-1]
|
||||
|
||||
stmt, err := ctx.Transaction.Preparex("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "prepare insert attachment")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute insert attachment")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAttachment returns the database attachment record specified by the parameters.
|
||||
func (s Scope) GetAttachment(ctx domain.RequestContext, orgID, attachmentID string) (a attachment.Attachment, err error) {
|
||||
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "prepare select attachment")
|
||||
return
|
||||
}
|
||||
|
||||
err = stmt.Get(&a, orgID, attachmentID)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute select attachment")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAttachments returns a slice containing the attachement records (excluding their data) for document docID, ordered by filename.
|
||||
func (s Scope) GetAttachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
|
||||
err = s.Runtime.Db.Select(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", ctx.OrgID, docID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute select attachments")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAttachmentsWithData returns a slice containing the attachement records (including their data) for document docID, ordered by filename.
|
||||
func (s Scope) GetAttachmentsWithData(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
|
||||
err = s.Runtime.Db.Select(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, data, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", ctx.OrgID, docID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute select attachments with data")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the id record from the database attachment table.
|
||||
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
return b.DeleteConstrained(ctx.Transaction, "attachment", ctx.OrgID, id)
|
||||
}
|
|
@ -11,13 +11,34 @@
|
|||
|
||||
package auth
|
||||
|
||||
/*
|
||||
// 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) {
|
||||
method := "Authenticate"
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
s := domain.StoreContext{Runtime: h.Runtime, Context: domain.GetRequestContext(r)}
|
||||
"github.com/documize/community/core/env"
|
||||
"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"
|
||||
"github.com/documize/community/model/auth"
|
||||
"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
|
||||
}
|
||||
|
||||
// Login user based up HTTP Authorization header.
|
||||
// An encrypted authentication token is issued with an expiry date.
|
||||
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
method := "auth.Login"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
// check for http header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
|
@ -46,23 +67,20 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
dom := strings.TrimSpace(strings.ToLower(credentials[0]))
|
||||
dom = organization.CheckDomain(s, dom) // TODO optimize by removing this once js allows empty domains
|
||||
dom = h.Store.Organization.CheckDomain(ctx, dom) // TODO optimize by removing this once js allows empty domains
|
||||
email := strings.TrimSpace(strings.ToLower(credentials[1]))
|
||||
password := credentials[2]
|
||||
h.Runtime.Log.Info("logon attempt " + email + " @ " + dom)
|
||||
|
||||
u, err := user.GetByDomain(s, dom, email)
|
||||
|
||||
u, err := h.Store.User.GetByDomain(ctx, dom, email)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(u.Reset) > 0 || len(u.Password) == 0 {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
|
@ -74,31 +92,29 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
org, err := organization.GetOrganizationByDomain(s, dom)
|
||||
org, err := h.Store.Organization.GetOrganizationByDomain(ctx, dom)
|
||||
if err != nil {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Attach user accounts and work out permissions
|
||||
user.AttachUserAccounts(s, org.RefID, &u)
|
||||
|
||||
// active check
|
||||
user.AttachUserAccounts(ctx, *h.Store, org.RefID, &u)
|
||||
|
||||
if len(u.Accounts) == 0 {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
authModel := AuthenticationModel{}
|
||||
authModel := auth.AuthenticationModel{}
|
||||
authModel.Token = GenerateJWT(h.Runtime, u.RefID, org.RefID, dom)
|
||||
authModel.User = u
|
||||
|
||||
response.WriteJSON(w, authModel)
|
||||
}
|
||||
|
||||
// ValidateAuthToken finds and validates authentication token.
|
||||
func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
|
||||
// ValidateToken finds and validates authentication token.
|
||||
func (h *Handler) ValidateToken(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO should this go after token validation?
|
||||
if s := r.URL.Query().Get("section"); s != "" {
|
||||
if err := provider.Callback(s, w, r); err != nil {
|
||||
|
@ -109,40 +125,40 @@ func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
s := domain.StoreContext{Runtime: h.Runtime, Context: domain.GetRequestContext(r)}
|
||||
|
||||
token := FindJWT(r)
|
||||
rc, _, tokenErr := DecodeJWT(h.Runtime, token)
|
||||
|
||||
var org = organization.Organization{}
|
||||
var org = org.Organization{}
|
||||
var err = errors.New("")
|
||||
|
||||
// We always grab the org record regardless of token status.
|
||||
// Why? If bad token we might be OK to alow anonymous access
|
||||
// depending upon the domain in question.
|
||||
if len(rc.OrgID) == 0 {
|
||||
org, err = organization.GetOrganizationByDomain(s, organization.GetRequestSubdomain(s, r))
|
||||
dom := organization.GetRequestSubdomain(r)
|
||||
org, err = h.Store.Organization.GetOrganizationByDomain(rc, dom)
|
||||
} else {
|
||||
org, err = organization.GetOrganization(s, rc.OrgID)
|
||||
org, err = h.Store.Organization.GetOrganization(rc, rc.OrgID)
|
||||
}
|
||||
|
||||
rc.Subdomain = org.Domain
|
||||
|
||||
// Inability to find org record spells the end of this request.
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// If we have bad auth token and the domain does not allow anon access
|
||||
if !org.AllowAnonymousAccess && tokenErr != nil {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -152,7 +168,7 @@ func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
|
|||
// So you have a bad token
|
||||
if len(token) > 1 {
|
||||
if tokenErr != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -170,18 +186,18 @@ func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
|
|||
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
|
||||
|
||||
// Fetch user permissions for this org
|
||||
if !rc.Authenticated {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := user.GetSecuredUser(s, org.RefID, rc.UserID)
|
||||
u, err := user.GetSecuredUser(rc, *h.Store, org.RefID, rc.UserID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -190,6 +206,4 @@ func (h *Handler) ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
|
|||
rc.Global = u.Global
|
||||
|
||||
response.WriteJSON(w, u)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
|
49
domain/auth/keycloak.go
Normal file
49
domain/auth/keycloak.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/model/auth"
|
||||
)
|
||||
|
||||
// StripAuthSecrets removes sensitive data from auth provider configuration
|
||||
func StripAuthSecrets(r *env.Runtime, provider, config string) string {
|
||||
switch provider {
|
||||
case "documize":
|
||||
return config
|
||||
break
|
||||
case "keycloak":
|
||||
c := auth.KeycloakConfig{}
|
||||
err := json.Unmarshal([]byte(config), &c)
|
||||
if err != nil {
|
||||
r.Log.Error("StripAuthSecrets", err)
|
||||
return config
|
||||
}
|
||||
c.AdminPassword = ""
|
||||
c.AdminUser = ""
|
||||
c.PublicKey = ""
|
||||
|
||||
j, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
r.Log.Error("StripAuthSecrets", err)
|
||||
return config
|
||||
}
|
||||
|
||||
return string(j)
|
||||
break
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package auth
|
||||
|
||||
/*
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
// AuthenticationModel details authentication token and user details.
|
||||
type AuthenticationModel struct {
|
||||
Token string `json:"token"`
|
||||
User user.User `json:"user"`
|
||||
}
|
||||
*/
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/streamutil"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -25,6 +26,25 @@ type Scope struct {
|
|||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
// Get fetches the document record with the given id fromt the document table and audits that it has been got.
|
||||
func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
|
||||
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? and refid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "prepare select document")
|
||||
return
|
||||
}
|
||||
|
||||
err = stmt.Get(&document, ctx.OrgID, id)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute select document")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MoveDocumentSpace changes the label for client's organization's documents which have space "id", to "move".
|
||||
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=?")
|
||||
|
@ -43,3 +63,20 @@ func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (er
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// PublicDocuments returns a slice of SitemapDocument records, holding documents in folders of type 1 (entity.TemplateTypePublic).
|
||||
func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error) {
|
||||
err = s.Runtime.Db.Select(&documents,
|
||||
`SELECT d.refid as documentid, d.title as document, d.revised as revised, l.refid as folderid, l.label as folder
|
||||
FROM document d LEFT JOIN label l ON l.refid=d.labelid
|
||||
WHERE d.orgid=?
|
||||
AND l.type=1
|
||||
AND d.template=0`, orgID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
116
domain/document/permission.go
Normal file
116
domain/document/permission.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package document
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
)
|
||||
|
||||
// CanViewDocumentInFolder returns if the user has permission to view a document within the specified folder.
|
||||
func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID string) (hasPermission bool) {
|
||||
roles, err := s.Space.GetUserRoles(ctx)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.LabelID == labelID && (role.CanView || role.CanEdit) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CanViewDocument returns if the clinet has permission to view a given document.
|
||||
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
|
||||
document, err := s.Document.Get(ctx, documentID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
roles, err := s.Space.GetUserRoles(ctx)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.LabelID == document.LabelID && (role.CanView || role.CanEdit) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CanChangeDocument returns if the clinet has permission to change a given document.
|
||||
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
|
||||
document, err := s.Document.Get(ctx, documentID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
roles, err := s.Space.GetUserRoles(ctx)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.LabelID == document.LabelID && role.CanEdit {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CanUploadDocument returns if the client has permission to upload documents to the given folderID.
|
||||
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, folderID string) (hasPermission bool) {
|
||||
roles, err := s.Space.GetUserRoles(ctx)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.LabelID == folderID && role.CanEdit {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
160
domain/link/endpoint.go
Normal file
160
domain/link/endpoint.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package link
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"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/document"
|
||||
"github.com/documize/community/model/attachment"
|
||||
"github.com/documize/community/model/link"
|
||||
"github.com/documize/community/model/page"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *domain.Store
|
||||
}
|
||||
|
||||
// GetLinkCandidates returns references to documents/sections/attachments.
|
||||
func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
|
||||
method := "link.Candidates"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
folderID := request.Param(r, "folderID")
|
||||
if len(folderID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "folderID")
|
||||
return
|
||||
}
|
||||
|
||||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
pageID := request.Param(r, "pageID")
|
||||
if len(pageID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
// permission check
|
||||
if document.CanViewDocument(ctx, *h.Store, documentID) {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// We can link to a section within the same document so
|
||||
// let's get all pages for the document and remove "us".
|
||||
pages, err := h.Store.Page.GetPagesWithoutContent(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pages) == 0 {
|
||||
pages = []page.Page{}
|
||||
}
|
||||
|
||||
pc := []link.Candidate{}
|
||||
|
||||
for _, p := range pages {
|
||||
if p.RefID != pageID {
|
||||
c := link.Candidate{
|
||||
RefID: uniqueid.Generate(),
|
||||
FolderID: folderID,
|
||||
DocumentID: documentID,
|
||||
TargetID: p.RefID,
|
||||
LinkType: p.PageType,
|
||||
Title: p.Title,
|
||||
}
|
||||
pc = append(pc, c)
|
||||
}
|
||||
}
|
||||
|
||||
// We can link to attachment within the same document so
|
||||
// let's get all attachments for the document.
|
||||
files, err := h.Store.Attachment.GetAttachments(ctx, documentID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
files = []attachment.Attachment{}
|
||||
}
|
||||
|
||||
fc := []link.Candidate{}
|
||||
|
||||
for _, f := range files {
|
||||
c := link.Candidate{
|
||||
RefID: uniqueid.Generate(),
|
||||
FolderID: folderID,
|
||||
DocumentID: documentID,
|
||||
TargetID: f.RefID,
|
||||
LinkType: "file",
|
||||
Title: f.Filename,
|
||||
Context: f.Extension,
|
||||
}
|
||||
|
||||
fc = append(fc, c)
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Pages []link.Candidate `json:"pages"`
|
||||
Attachments []link.Candidate `json:"attachments"`
|
||||
}
|
||||
|
||||
payload.Pages = pc
|
||||
payload.Attachments = fc
|
||||
|
||||
response.WriteJSON(w, payload)
|
||||
}
|
||||
|
||||
// SearchLinkCandidates endpoint takes a list of keywords and returns a list of document references matching those keywords.
|
||||
func (h *Handler) SearchLinkCandidates(w http.ResponseWriter, r *http.Request) {
|
||||
method := "link.SearchLinkCandidates"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
keywords := request.Query(r, "keywords")
|
||||
decoded, err := url.QueryUnescape(keywords)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("decode query string", err)
|
||||
}
|
||||
|
||||
docs, pages, attachments, err := h.Store.Link.SearchCandidates(ctx, decoded)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Documents []link.Candidate `json:"documents"`
|
||||
Pages []link.Candidate `json:"pages"`
|
||||
Attachments []link.Candidate `json:"attachments"`
|
||||
}
|
||||
|
||||
payload.Documents = docs
|
||||
payload.Pages = pages
|
||||
payload.Attachments = attachments
|
||||
|
||||
response.WriteJSON(w, payload)
|
||||
}
|
|
@ -14,13 +14,13 @@ package link
|
|||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/model/link"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// GetContentLinks returns Documize generated <a> links.
|
||||
// such links have an identifying attribute e.g. <a data-documize='true'...
|
||||
func GetContentLinks(body string) (links []entity.Link) {
|
||||
func GetContentLinks(body string) (links []link.Link) {
|
||||
z := html.NewTokenizer(strings.NewReader(body))
|
||||
|
||||
for {
|
||||
|
@ -49,7 +49,7 @@ func GetContentLinks(body string) (links []entity.Link) {
|
|||
}
|
||||
|
||||
// Helper function to pull the href attribute from a Token
|
||||
func getLink(t html.Token) (ok bool, link entity.Link) {
|
||||
func getLink(t html.Token) (ok bool, link link.Link) {
|
||||
ok = false
|
||||
|
||||
// Iterate over all of the Token's attributes until we find an "href"
|
||||
|
|
292
domain/link/mysql/store.go
Normal file
292
domain/link/mysql/store.go
Normal file
|
@ -0,0 +1,292 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package link
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/streamutil"
|
||||
"github.com/documize/community/core/uniqueid"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store/mysql"
|
||||
"github.com/documize/community/model/link"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Scope provides data access to MySQL.
|
||||
type Scope struct {
|
||||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
// Add inserts wiki-link into the store.
|
||||
// These links exist when content references another document or content.
|
||||
func (s Scope) Add(ctx domain.RequestContext, l link.Link) (err error) {
|
||||
l.Created = time.Now().UTC()
|
||||
l.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := ctx.Transaction.Preparex("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "prepare link insert")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute link insert")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocumentOutboundLinks returns outbound links for specified document.
|
||||
func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID string) (links []link.Link, err error) {
|
||||
err = s.Runtime.Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
WHERE l.orgid=? AND l.sourcedocumentid=?`,
|
||||
ctx.OrgID,
|
||||
documentID)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(links) == 0 {
|
||||
links = []link.Link{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPageLinks returns outbound links for specified page in document.
|
||||
func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string) (links []link.Link, err error) {
|
||||
err = s.Runtime.Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`,
|
||||
ctx.OrgID,
|
||||
documentID,
|
||||
pageID)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(links) == 0 {
|
||||
links = []link.Link{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanDocumentLink marks all link records referencing specified document.
|
||||
func (s Scope) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(revised, ctx.OrgID, documentID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanPageLink marks all link records referencing specified page.
|
||||
func (s Scope) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(revised, ctx.OrgID, pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanAttachmentLink marks all link records referencing specified attachment.
|
||||
func (s Scope) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?")
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(revised, ctx.OrgID, attachmentID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteSourcePageLinks removes saved links for given source.
|
||||
func (s Scope) DeleteSourcePageLinks(ctx domain.RequestContext, pageID string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcepageid=\"%s\"", ctx.OrgID, pageID))
|
||||
}
|
||||
|
||||
// DeleteSourceDocumentLinks removes saved links for given document.
|
||||
func (s Scope) DeleteSourceDocumentLinks(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcedocumentid=\"%s\"", ctx.OrgID, documentID))
|
||||
}
|
||||
|
||||
// DeleteLink removes saved link from the store.
|
||||
func (s Scope) DeleteLink(ctx domain.RequestContext, id string) (rows int64, err error) {
|
||||
b := mysql.BaseQuery{}
|
||||
return b.DeleteConstrained(ctx.Transaction, "link", ctx.OrgID, id)
|
||||
}
|
||||
|
||||
// SearchCandidates returns matching documents, sections and attachments using keywords.
|
||||
func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (docs []link.Candidate,
|
||||
pages []link.Candidate, attachments []link.Candidate, err error) {
|
||||
// find matching documents
|
||||
temp := []link.Candidate{}
|
||||
likeQuery := "title LIKE '%" + keywords + "%'"
|
||||
|
||||
err = s.Runtime.Db.Select(&temp,
|
||||
`SELECT refid as documentid, labelid as folderid,title from document WHERE orgid=? AND `+likeQuery+` AND labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY title`,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute search links 1")
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := link.Candidate{
|
||||
RefID: uniqueid.Generate(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.DocumentID,
|
||||
LinkType: "document",
|
||||
Title: r.Title,
|
||||
Context: "",
|
||||
}
|
||||
|
||||
docs = append(docs, c)
|
||||
}
|
||||
|
||||
// find matching sections
|
||||
likeQuery = "p.title LIKE '%" + keywords + "%'"
|
||||
temp = []link.Candidate{}
|
||||
|
||||
err = s.Runtime.Db.Select(&temp,
|
||||
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid from page p
|
||||
LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY p.title`,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute search links 2")
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := link.Candidate{
|
||||
RefID: uniqueid.Generate(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.TargetID,
|
||||
LinkType: r.LinkType,
|
||||
Title: r.Title,
|
||||
Context: r.Context,
|
||||
}
|
||||
|
||||
pages = append(pages, c)
|
||||
}
|
||||
|
||||
// find matching attachments
|
||||
likeQuery = "a.filename LIKE '%" + keywords + "%'"
|
||||
temp = []link.Candidate{}
|
||||
|
||||
err = s.Runtime.Db.Select(&temp,
|
||||
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid from attachment a
|
||||
LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY a.filename`,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute search links 3")
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := link.Candidate{
|
||||
RefID: uniqueid.Generate(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.TargetID,
|
||||
LinkType: "file",
|
||||
Title: r.Title,
|
||||
Context: r.Context,
|
||||
}
|
||||
|
||||
attachments = append(attachments, c)
|
||||
}
|
||||
|
||||
if len(docs) == 0 {
|
||||
docs = []link.Candidate{}
|
||||
}
|
||||
if len(pages) == 0 {
|
||||
pages = []link.Candidate{}
|
||||
}
|
||||
if len(attachments) == 0 {
|
||||
attachments = []link.Candidate{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
182
domain/meta/endpoint.go
Normal file
182
domain/meta/endpoint.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/auth"
|
||||
"github.com/documize/community/domain/organization"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/org"
|
||||
"github.com/documize/community/model/space"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *domain.Store
|
||||
}
|
||||
|
||||
// Meta provides org meta data based upon request domain (e.g. acme.documize.com).
|
||||
func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
data := org.SiteMeta{}
|
||||
data.URL = organization.GetSubdomainFromHost(r)
|
||||
|
||||
org, err := h.Store.Organization.GetOrganizationByDomain(ctx, data.URL)
|
||||
if err != nil {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
data.OrgID = org.RefID
|
||||
data.Title = org.Title
|
||||
data.Message = org.Message
|
||||
data.AllowAnonymousAccess = org.AllowAnonymousAccess
|
||||
data.AuthProvider = org.AuthProvider
|
||||
data.AuthConfig = org.AuthConfig
|
||||
data.Version = h.Runtime.Product.Version
|
||||
data.Edition = h.Runtime.Product.License.Edition
|
||||
data.Valid = h.Runtime.Product.License.Valid
|
||||
data.ConversionEndpoint = org.ConversionEndpoint
|
||||
|
||||
// Strip secrets
|
||||
data.AuthConfig = auth.StripAuthSecrets(h.Runtime, org.AuthProvider, org.AuthConfig)
|
||||
|
||||
response.WriteJSON(w, data)
|
||||
}
|
||||
|
||||
// RobotsTxt returns robots.txt depending on site configuration.
|
||||
// Did we allow anonymouse access?
|
||||
func (h *Handler) RobotsTxt(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetRobots"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
dom := organization.GetSubdomainFromHost(r)
|
||||
org, err := h.Store.Organization.GetOrganizationByDomain(ctx, dom)
|
||||
|
||||
// default is to deny
|
||||
robots :=
|
||||
`User-agent: *
|
||||
Disallow: /
|
||||
`
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get Organization for domain %s", method, dom), err)
|
||||
}
|
||||
|
||||
// Anonymous access would mean we allow bots to crawl.
|
||||
if org.AllowAnonymousAccess {
|
||||
sitemap := ctx.GetAppURL("sitemap.xml")
|
||||
robots = fmt.Sprintf(
|
||||
`User-agent: *
|
||||
Disallow: /settings/
|
||||
Disallow: /settings/*
|
||||
Disallow: /profile/
|
||||
Disallow: /profile/*
|
||||
Disallow: /auth/login/
|
||||
Disallow: /auth/login/
|
||||
Disallow: /auth/logout/
|
||||
Disallow: /auth/logout/*
|
||||
Disallow: /auth/reset/*
|
||||
Disallow: /auth/reset/*
|
||||
Disallow: /auth/sso/
|
||||
Disallow: /auth/sso/*
|
||||
Disallow: /share
|
||||
Disallow: /share/*
|
||||
Sitemap: %s`, sitemap)
|
||||
}
|
||||
|
||||
response.WriteBytes(w, []byte(robots))
|
||||
}
|
||||
|
||||
// Sitemap returns URLs that can be indexed.
|
||||
// We only include public folders and documents (e.g. can be seen by everyone).
|
||||
func (h *Handler) Sitemap(w http.ResponseWriter, r *http.Request) {
|
||||
method := "meta.Sitemap"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
dom := organization.GetSubdomainFromHost(r)
|
||||
org, err := h.Store.Organization.GetOrganizationByDomain(ctx, dom)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get Organization for domain %s", method, dom), err)
|
||||
}
|
||||
|
||||
sitemap :=
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
{{range .}}<url>
|
||||
<loc>{{ .URL }}</loc>
|
||||
<lastmod>{{ .Date }}</lastmod>
|
||||
</url>{{end}}
|
||||
</urlset>`
|
||||
|
||||
var items []sitemapItem
|
||||
|
||||
// Anonymous access means we announce folders/documents shared with 'Everyone'.
|
||||
if org.AllowAnonymousAccess {
|
||||
// Grab shared folders
|
||||
folders, err := h.Store.Space.PublicSpaces(ctx, org.RefID)
|
||||
if err != nil {
|
||||
folders = []space.Space{}
|
||||
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get folders for domain %s", method, dom), err)
|
||||
}
|
||||
|
||||
for _, folder := range folders {
|
||||
var item sitemapItem
|
||||
item.URL = ctx.GetAppURL(fmt.Sprintf("s/%s/%s", folder.RefID, stringutil.MakeSlug(folder.Name)))
|
||||
item.Date = folder.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
// Grab documents from shared folders
|
||||
var documents []doc.SitemapDocument
|
||||
documents, err = h.Store.Document.PublicDocuments(ctx, org.RefID)
|
||||
if err != nil {
|
||||
documents = []doc.SitemapDocument{}
|
||||
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get documents for domain %s", method, dom), err)
|
||||
}
|
||||
|
||||
for _, document := range documents {
|
||||
var item sitemapItem
|
||||
item.URL = ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
|
||||
document.FolderID, stringutil.MakeSlug(document.Folder), document.DocumentID, stringutil.MakeSlug(document.Document)))
|
||||
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
t := template.Must(template.New("tmp").Parse(sitemap))
|
||||
log.IfErr(t.Execute(buffer, &items))
|
||||
|
||||
response.WriteBytes(w, buffer.Bytes())
|
||||
}
|
||||
|
||||
// sitemapItem provides a means to teleport somewhere else for free.
|
||||
// What did you think it did?
|
||||
type sitemapItem struct {
|
||||
URL string
|
||||
Date string
|
||||
}
|
39
domain/page/mysql/store.go
Normal file
39
domain/page/mysql/store.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/model/page"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Scope provides data access to MySQL.
|
||||
type Scope struct {
|
||||
Runtime *env.Runtime
|
||||
}
|
||||
|
||||
// GetPagesWithoutContent returns a slice containing all the page records for a given documentID, in presentation sequence,
|
||||
// but without the body field (which holds the HTML content).
|
||||
func (s Scope) GetPagesWithoutContent(ctx domain.RequestContext, documentID string) (pages []page.Page, err error) {
|
||||
err = s.Runtime.Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, pagetype, sequence, level, title, revisions, blockid, created, revised FROM page WHERE orgid=? AND documentid=? ORDER BY sequence", ctx.OrgID, documentID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
248
domain/setting/endpoint.go
Normal file
248
domain/setting/endpoint.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
// Package setting manages both global and user level settings
|
||||
package setting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/event"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/model/audit"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *domain.Store
|
||||
}
|
||||
|
||||
// SMTP returns installation-wide SMTP settings
|
||||
func (h *Handler) SMTP(w http.ResponseWriter, r *http.Request) {
|
||||
method := "setting.SMTP"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
config := h.Store.Setting.Get(ctx, "SMTP", "")
|
||||
|
||||
var y map[string]interface{}
|
||||
json.Unmarshal([]byte(config), &y)
|
||||
|
||||
j, err := json.Marshal(y)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteBytes(w, j)
|
||||
}
|
||||
|
||||
// SetSMTP persists global SMTP configuration.
|
||||
func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) {
|
||||
method := "setting.SetSMTP"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var config string
|
||||
config = string(body)
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Setting.Set(ctx, "SMTP", config)
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSystemSMTP)
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// License returns product license
|
||||
func (h *Handler) License(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
config := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "")
|
||||
if len(config) == 0 {
|
||||
config = "{}"
|
||||
}
|
||||
|
||||
x := &licenseXML{Key: "", Signature: ""}
|
||||
lj := licenseJSON{}
|
||||
|
||||
err := json.Unmarshal([]byte(config), &lj)
|
||||
if err == nil {
|
||||
x.Key = lj.Key
|
||||
x.Signature = lj.Signature
|
||||
} else {
|
||||
h.Runtime.Log.Error("failed to JSON unmarshal EDITION-LICENSE", err)
|
||||
}
|
||||
|
||||
output, err := xml.Marshal(x)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("failed to XML marshal EDITION-LICENSE", err)
|
||||
}
|
||||
|
||||
response.WriteBytes(w, output)
|
||||
}
|
||||
|
||||
// SetLicense persists product license
|
||||
func (h *Handler) SetLicense(w http.ResponseWriter, r *http.Request) {
|
||||
method := "setting.SetLicense"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var config string
|
||||
config = string(body)
|
||||
lj := licenseJSON{}
|
||||
x := licenseXML{Key: "", Signature: ""}
|
||||
|
||||
err = xml.Unmarshal([]byte(config), &x)
|
||||
if err == nil {
|
||||
lj.Key = x.Key
|
||||
lj.Signature = x.Signature
|
||||
} else {
|
||||
h.Runtime.Log.Error("failed to XML unmarshal EDITION-LICENSE", err)
|
||||
}
|
||||
|
||||
j, err := json.Marshal(lj)
|
||||
js := "{}"
|
||||
if err == nil {
|
||||
js = string(j)
|
||||
}
|
||||
|
||||
h.Store.Setting.Set(ctx, "EDITION-LICENSE", js)
|
||||
|
||||
event.Handler().Publish(string(event.TypeSystemLicenseChange))
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSystemLicense)
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// AuthConfig returns installation-wide auth configuration
|
||||
func (h *Handler) AuthConfig(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||
if err != nil {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteJSON(w, org.AuthConfig)
|
||||
}
|
||||
|
||||
// SetAuthConfig persists installation-wide authentication configuration
|
||||
func (h *Handler) SetAuthConfig(w http.ResponseWriter, r *http.Request) {
|
||||
method := "SaveAuthConfig"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !ctx.Global {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var data authData
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
org.AuthProvider = data.AuthProvider
|
||||
org.AuthConfig = data.AuthConfig
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Store.Organization.UpdateAuthConfig(ctx, org)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSystemAuth)
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
38
domain/setting/model.go
Normal file
38
domain/setting/model.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
// Package setting manages both global and user level settings
|
||||
package setting
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type licenseXML struct {
|
||||
XMLName xml.Name `xml:"DocumizeLicense"`
|
||||
Key string
|
||||
Signature string
|
||||
}
|
||||
|
||||
type licenseJSON struct {
|
||||
Key string `json:"key"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type authData struct {
|
||||
AuthProvider string `json:"authProvider"`
|
||||
AuthConfig string `json:"authConfig"`
|
||||
}
|
||||
|
||||
/*
|
||||
<DocumizeLicense>
|
||||
<Key>some key</Key>
|
||||
<Signature>some signature</Signature>
|
||||
</DocumizeLicense>
|
||||
*/
|
135
domain/setting/mysql/setting.go
Normal file
135
domain/setting/mysql/setting.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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
|
||||
}
|
||||
|
||||
// Get fetches a configuration JSON element from the config table.
|
||||
func (s Scope) Get(ctx domain.RequestContext, area, path string) (value string) {
|
||||
if path != "" {
|
||||
path = "." + path
|
||||
}
|
||||
sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `config` WHERE `key` = '" + area + "';"
|
||||
|
||||
stmt, err := s.Runtime.Db.Preparex(sql)
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
s.Runtime.Log.Error(fmt.Sprintf("setting.Get %s %s", area, path), err)
|
||||
return ""
|
||||
}
|
||||
|
||||
var item = make([]uint8, 0)
|
||||
|
||||
err = stmt.Get(&item)
|
||||
if err != nil {
|
||||
s.Runtime.Log.Error(fmt.Sprintf("setting.Get %s %s", area, path), err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(item) > 1 {
|
||||
q := []byte(`"`)
|
||||
value = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// Set writes a configuration JSON element to the config table.
|
||||
func (s Scope) Set(ctx domain.RequestContext, area, json string) error {
|
||||
if area == "" {
|
||||
return errors.New("no area")
|
||||
}
|
||||
|
||||
sql := "INSERT INTO `config` (`key`,`config`) " +
|
||||
"VALUES ('" + area + "','" + json +
|
||||
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||
|
||||
stmt, err := s.Runtime.Db.Preparex(sql)
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to save global config value")
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUser fetches a configuration JSON element from the userconfig table for a given orgid/userid combination.
|
||||
// Errors return the empty string. A blank path returns the whole JSON object, as JSON.
|
||||
func (s Scope) GetUser(ctx domain.RequestContext, orgID, userID, area, path string) (value string) {
|
||||
if path != "" {
|
||||
path = "." + path
|
||||
}
|
||||
|
||||
sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `userconfig` WHERE `key` = '" + area +
|
||||
"' AND `orgid` = '" + orgID + "' AND `userid` = '" + userID + "';"
|
||||
|
||||
stmt, err := s.Runtime.Db.Preparex(sql)
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var item = make([]uint8, 0)
|
||||
|
||||
err = stmt.Get(&item)
|
||||
if err != nil {
|
||||
s.Runtime.Log.Error(fmt.Sprintf("setting.GetUser for user %s %s %s", userID, area, path), err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(item) > 1 {
|
||||
q := []byte(`"`)
|
||||
value = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// SetUser writes a configuration JSON element to the userconfig table for the current user.
|
||||
func (s Scope) SetUser(ctx domain.RequestContext, orgID, userID, area, json string) error {
|
||||
if area == "" {
|
||||
return errors.New("no area")
|
||||
}
|
||||
|
||||
sql := "INSERT INTO `userconfig` (`orgid`,`userid`,`key`,`config`) " +
|
||||
"VALUES ('" + orgID + "','" + userID + "','" + area + "','" + json +
|
||||
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||
|
||||
stmt, err := s.Runtime.Db.Preparex(sql)
|
||||
defer streamutil.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
|
||||
return err
|
||||
}
|
|
@ -14,8 +14,12 @@ package domain
|
|||
|
||||
import (
|
||||
"github.com/documize/community/model/account"
|
||||
"github.com/documize/community/model/attachment"
|
||||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/link"
|
||||
"github.com/documize/community/model/org"
|
||||
"github.com/documize/community/model/page"
|
||||
"github.com/documize/community/model/pin"
|
||||
"github.com/documize/community/model/space"
|
||||
"github.com/documize/community/model/user"
|
||||
|
@ -30,6 +34,10 @@ type Store struct {
|
|||
Pin PinStorer
|
||||
Audit AuditStorer
|
||||
Document DocumentStorer
|
||||
Setting SettingStorer
|
||||
Attachment AttachmentStorer
|
||||
Link LinkStorer
|
||||
Page PageStorer
|
||||
}
|
||||
|
||||
// SpaceStorer defines required methods for space management
|
||||
|
@ -112,7 +120,43 @@ type AuditStorer interface {
|
|||
|
||||
// DocumentStorer defines required methods for document handling
|
||||
type DocumentStorer interface {
|
||||
Get(ctx RequestContext, id string) (document doc.Document, err error)
|
||||
MoveDocumentSpace(ctx RequestContext, id, move string) (err error)
|
||||
PublicDocuments(ctx RequestContext, orgID string) (documents []doc.SitemapDocument, err error)
|
||||
}
|
||||
|
||||
// https://github.com/golang-sql/sqlexp/blob/c2488a8be21d20d31abf0d05c2735efd2d09afe4/quoter.go#L46
|
||||
// SettingStorer defines required methods for persisting global and user level settings
|
||||
type SettingStorer interface {
|
||||
Get(ctx RequestContext, area, path string) string
|
||||
Set(ctx RequestContext, area, value string) error
|
||||
GetUser(ctx RequestContext, orgID, userID, area, path string) string
|
||||
SetUser(ctx RequestContext, orgID, userID, area, json string) error
|
||||
}
|
||||
|
||||
// AttachmentStorer defines required methods for persisting document attachments
|
||||
type AttachmentStorer interface {
|
||||
Add(ctx RequestContext, a attachment.Attachment) (err error)
|
||||
GetAttachment(ctx RequestContext, orgID, attachmentID string) (a attachment.Attachment, err error)
|
||||
GetAttachments(ctx RequestContext, docID string) (a []attachment.Attachment, err error)
|
||||
GetAttachmentsWithData(ctx RequestContext, docID string) (a []attachment.Attachment, err error)
|
||||
Delete(ctx RequestContext, id string) (rows int64, err error)
|
||||
}
|
||||
|
||||
// LinkStorer defines required methods for persisting content links
|
||||
type LinkStorer interface {
|
||||
Add(ctx RequestContext, l link.Link) (err error)
|
||||
SearchCandidates(ctx RequestContext, keywords string) (docs []link.Candidate, pages []link.Candidate, attachments []link.Candidate, err error)
|
||||
GetDocumentOutboundLinks(ctx RequestContext, documentID string) (links []link.Link, err error)
|
||||
GetPageLinks(ctx RequestContext, documentID, pageID string) (links []link.Link, err error)
|
||||
MarkOrphanDocumentLink(ctx RequestContext, documentID string) (err error)
|
||||
MarkOrphanPageLink(ctx RequestContext, pageID string) (err error)
|
||||
MarkOrphanAttachmentLink(ctx RequestContext, attachmentID string) (err error)
|
||||
DeleteSourcePageLinks(ctx RequestContext, pageID string) (rows int64, err error)
|
||||
DeleteSourceDocumentLinks(ctx RequestContext, documentID string) (rows int64, err error)
|
||||
DeleteLink(ctx RequestContext, id string) (rows int64, err error)
|
||||
}
|
||||
|
||||
// PageStorer defines required methods for persisting document pages
|
||||
type PageStorer interface {
|
||||
GetPagesWithoutContent(ctx RequestContext, documentID string) (pages []page.Page, err error)
|
||||
}
|
||||
|
|
0
domain/template/mysql/store.go
Normal file
0
domain/template/mysql/store.go
Normal file
|
@ -12,27 +12,48 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/documize/community/core/api/mail"
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/event"
|
||||
"github.com/documize/community/core/request"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/secrets"
|
||||
"github.com/documize/community/core/streamutil"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
"github.com/documize/community/core/uniqueid"
|
||||
"github.com/documize/community/domain"
|
||||
"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
|
||||
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"
|
||||
// Add is the endpoint that enables an administrator to add a new user for their orgaisation.
|
||||
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.Add"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !h.Runtime.Product.License.IsValid() {
|
||||
response.WriteBadLicense(w)
|
||||
}
|
||||
|
||||
if !s.Context.Administrator {
|
||||
if !ctx.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
@ -44,7 +65,7 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
userModel := model.User{}
|
||||
userModel := user.User{}
|
||||
err = json.Unmarshal(body, &userModel)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
|
@ -82,7 +103,7 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
addAccount := true
|
||||
var userID string
|
||||
|
||||
userDupe, err := h.Store.User.GetByEmail(s, userModel.Email)
|
||||
userDupe, err := h.Store.User.GetByEmail(ctx, userModel.Email)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -95,7 +116,7 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
h.Runtime.Log.Info("Dupe user found, will not add " + userModel.Email)
|
||||
}
|
||||
|
||||
s.Context.Transaction, err = request.Db.Beginx()
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -105,19 +126,19 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
userID = uniqueid.Generate()
|
||||
userModel.RefID = userID
|
||||
|
||||
err = h.Store.User.Add(s, userModel)
|
||||
err = h.Store.User.Add(ctx, userModel)
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Runtime.Log.Info("Adding user")
|
||||
} else {
|
||||
AttachUserAccounts(s, s.Context.OrgID, &userDupe)
|
||||
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &userDupe)
|
||||
|
||||
for _, a := range userDupe.Accounts {
|
||||
if a.OrgID == s.Context.OrgID {
|
||||
if a.OrgID == ctx.OrgID {
|
||||
addAccount = false
|
||||
h.Runtime.Log.Info("Dupe account found, will not add")
|
||||
break
|
||||
|
@ -127,17 +148,17 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// set up user account for the org
|
||||
if addAccount {
|
||||
var a model.Account
|
||||
var a account.Account
|
||||
a.RefID = uniqueid.Generate()
|
||||
a.UserID = userID
|
||||
a.OrgID = s.Context.OrgID
|
||||
a.OrgID = ctx.OrgID
|
||||
a.Editor = true
|
||||
a.Admin = false
|
||||
a.Active = true
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -145,15 +166,15 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if addUser {
|
||||
event.Handler().Publish(string(event.TypeAddUser))
|
||||
eventing.Record(s, eventing.EventTypeUserAdd)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeUserAdd)
|
||||
}
|
||||
|
||||
if addAccount {
|
||||
event.Handler().Publish(string(event.TypeAddAccount))
|
||||
eventing.Record(s, eventing.EventTypeAccountAdd)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeAccountAdd)
|
||||
}
|
||||
|
||||
s.Context.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
// If we did not add user or give them access (account) then we error back
|
||||
if !addUser && !addAccount {
|
||||
|
@ -162,7 +183,7 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Invite new user
|
||||
inviter, err := h.Store.User.Get(s, s.Context.UserID)
|
||||
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -172,16 +193,16 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
if addUser && addAccount {
|
||||
size := len(requestedPassword)
|
||||
|
||||
auth := fmt.Sprintf("%s:%s:%s", s.Context.AppURL, userModel.Email, requestedPassword[:size])
|
||||
auth := fmt.Sprintf("%s:%s:%s", ctx.AppURL, userModel.Email, requestedPassword[:size])
|
||||
encrypted := secrets.EncodeBase64([]byte(auth))
|
||||
|
||||
url := fmt.Sprintf("%s/%s", s.Context.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
|
||||
url := fmt.Sprintf("%s/%s", ctx.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))
|
||||
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
|
||||
|
||||
} else {
|
||||
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), s.Context.GetAppURL(""))
|
||||
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
|
||||
|
||||
h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
|
||||
}
|
||||
|
@ -189,33 +210,32 @@ func (h *Handler) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
method := "user.GetOrganizationUsers"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !s.Context.Editor && !s.Context.Administrator {
|
||||
if !ctx.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
active, err := strconv.ParseBool(request.Query("active"))
|
||||
active, err := strconv.ParseBool(request.Query(r, "active"))
|
||||
if err != nil {
|
||||
active = false
|
||||
}
|
||||
|
||||
u := []User{}
|
||||
u := []user.User{}
|
||||
|
||||
if active {
|
||||
u, err = GetActiveUsersForOrganization(s)
|
||||
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
u, err = GetUsersForOrganization(s)
|
||||
u, err = h.Store.User.GetUsersForOrganization(ctx)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -223,11 +243,11 @@ func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if len(u) == 0 {
|
||||
u = []User{}
|
||||
u = []user.User{}
|
||||
}
|
||||
|
||||
for i := range u {
|
||||
AttachUserAccounts(s, s.Context.OrgID, &u[i])
|
||||
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &u[i])
|
||||
}
|
||||
|
||||
response.WriteJSON(w, u)
|
||||
|
@ -236,19 +256,19 @@ func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
|
|||
// 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)
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
var u []User
|
||||
var u []user.User
|
||||
var err error
|
||||
|
||||
folderID := request.Param("folderID")
|
||||
folderID := request.Param(r, "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)
|
||||
folder, err := h.Store.Space.Get(ctx, folderID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
h.Runtime.Log.Error("cannot get space", err)
|
||||
response.WriteJSON(w, u)
|
||||
|
@ -256,22 +276,22 @@ func (h *Handler) GetSpaceUsers(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch folder.Type {
|
||||
case entity.FolderTypePublic:
|
||||
u, err = GetActiveUsersForOrganization(s)
|
||||
case space.ScopePublic:
|
||||
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
|
||||
break
|
||||
case entity.FolderTypePrivate:
|
||||
case space.ScopePrivate:
|
||||
// just me
|
||||
var me User
|
||||
user, err = Get(s, s.Context.UserID)
|
||||
var me user.User
|
||||
me, err = h.Store.User.Get(ctx, ctx.UserID)
|
||||
u = append(u, me)
|
||||
break
|
||||
case entity.FolderTypeRestricted:
|
||||
u, err = GetSpaceUsers(s, folderID)
|
||||
case space.ScopeRestricted:
|
||||
u, err = h.Store.User.GetSpaceUsers(ctx, folderID)
|
||||
break
|
||||
}
|
||||
|
||||
if len(u) == 0 {
|
||||
u = []User
|
||||
u = []user.User{}
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
|
@ -283,25 +303,25 @@ func (h *Handler) GetSpaceUsers(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
// Get returns user specified by ID
|
||||
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.Get"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
userID := request.Param("userID")
|
||||
userID := request.Param(r, "userID")
|
||||
if len(userID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "userId")
|
||||
return
|
||||
}
|
||||
|
||||
if userID != s.Context.UserID {
|
||||
if userID != ctx.UserID {
|
||||
response.WriteBadRequestError(w, method, "userId mismatch")
|
||||
return
|
||||
}
|
||||
|
||||
u, err := GetSecuredUser(s, s.Context.OrgID, userID)
|
||||
u, err := GetSecuredUser(ctx, *h.Store, ctx.OrgID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
response.WriteNotFoundError(s, method, s.Context.UserID)
|
||||
response.WriteNotFoundError(w, method, ctx.UserID)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -309,66 +329,63 @@ func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
response.WriteJSON(u)
|
||||
response.WriteJSON(w, 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)
|
||||
// Delete is the endpoint to delete a user specified by userID, the caller must be an Administrator.
|
||||
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.Delete"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
if !s.Context.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
userID := response.Params("userID")
|
||||
userID := request.Param(r, "userID")
|
||||
if len(userID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "userID")
|
||||
response.WriteMissingDataError(w, method, "userId")
|
||||
return
|
||||
}
|
||||
|
||||
if userID == s.Context.UserID {
|
||||
if userID == ctx.UserID {
|
||||
response.WriteBadRequestError(w, method, "cannot delete self")
|
||||
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 = DeactiveUser(s, userID)
|
||||
err = h.Store.User.DeactiveUser(ctx, userID)
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = space.ChangeLabelOwner(s, userID, s.Context.UserID)
|
||||
err = h.Store.Space.ChangeOwner(ctx, userID, ctx.UserID)
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
eventing.Record(s, eventing.EventTypeUserDelete)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeUserDelete)
|
||||
|
||||
event.Handler().Publish(string(event.TypeRemoveUser))
|
||||
|
||||
s.Context.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty()
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// UpdateUser is the endpoint to update user information for the given userID.
|
||||
// Update 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)
|
||||
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.Update"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
userID := request.Param("userID")
|
||||
userID := request.Param(r, "userID")
|
||||
if len(userID) == 0 {
|
||||
response.WriteBadRequestError(w, method, "user id must be numeric")
|
||||
return
|
||||
|
@ -377,11 +394,11 @@ func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WritePayloadError(w, method, err)
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
u := User{}
|
||||
u := user.User{}
|
||||
err = json.Unmarshal(body, &u)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
|
@ -389,7 +406,7 @@ func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// can only update your own account unless you are an admin
|
||||
if s.Context.UserID != userID && !s.Context.Administrator {
|
||||
if ctx.UserID != userID && !ctx.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
@ -400,7 +417,7 @@ func (h *Handler) UpdateUser(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
|
||||
|
@ -409,9 +426,9 @@ func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
u.RefID = userID
|
||||
u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname)
|
||||
|
||||
err = 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
|
||||
}
|
||||
|
@ -419,9 +436,9 @@ func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
// 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)
|
||||
a, err := h.Store.Account.GetUserAccount(ctx, userID)
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
@ -430,26 +447,26 @@ func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
a.Admin = u.Admin
|
||||
a.Active = u.Active
|
||||
|
||||
err = account.UpdateAccount(s, account)
|
||||
err = h.Store.Account.UpdateAccount(ctx, a)
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
eventing.Record(s, eventing.EventTypeUserUpdate)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeUserUpdate)
|
||||
|
||||
s.Context.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteJSON(u)
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// 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)
|
||||
// ChangePassword accepts password change from within the app.
|
||||
func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.ChangePassword"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
userID := response.Param("userID")
|
||||
userID := request.Param(r, "userID")
|
||||
if len(userID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "user id")
|
||||
return
|
||||
|
@ -464,18 +481,18 @@ func (h *Handler) ChangeUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
newPassword := string(body)
|
||||
|
||||
// can only update your own account unless you are an admin
|
||||
if userID != s.Context.UserID && !s.Context.Administrator {
|
||||
if userID != ctx.UserID || !ctx.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
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
|
||||
}
|
||||
|
||||
u, err := Get(s, userID)
|
||||
u, err := h.Store.User.Get(ctx, userID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -483,28 +500,29 @@ func (h *Handler) ChangeUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
u.Salt = secrets.GenerateSalt()
|
||||
|
||||
err = UpdateUserPassword(s, userID, user.Salt, secrets.GeneratePassword(newPassword, user.Salt))
|
||||
err = h.Store.User.UpdateUserPassword(ctx, userID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt))
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.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)
|
||||
// UserSpacePermissions returns folder permission for authenticated user.
|
||||
func (h *Handler) UserSpacePermissions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.UserSpacePermissions"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
userID := request.Param("userID")
|
||||
if userID != p.Context.UserID {
|
||||
userID := request.Param(r, "userID")
|
||||
if userID != ctx.UserID {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := space.GetUserLabelRoles(s, userID)
|
||||
roles, err := h.Store.Space.GetUserRoles(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
roles = []space.Role{}
|
||||
|
@ -517,12 +535,12 @@ func (h *Handler) GetUserFolderPermissions(w http.ResponseWriter, r *http.Reques
|
|||
response.WriteJSON(w, roles)
|
||||
}
|
||||
|
||||
// ForgotUserPassword initiates the change password procedure.
|
||||
// ForgotPassword 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)
|
||||
func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.ForgotPassword"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
@ -531,14 +549,14 @@ func (h *Handler) ForgotUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
u := new(User)
|
||||
u := new(user.User)
|
||||
err = json.Unmarshal(body, &u)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "JSON body")
|
||||
return
|
||||
}
|
||||
|
||||
s.Context.Transaction, err = request.Db.Beginx()
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
|
@ -546,33 +564,33 @@ func (h *Handler) ForgotUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
token := secrets.GenerateSalt()
|
||||
|
||||
err = ForgotUserPassword(s, u.Email, token)
|
||||
err = h.Store.User.ForgotUserPassword(ctx, u.Email, token)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.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))
|
||||
h.Runtime.Log.Info(fmt.Sprintf("User %s not found for password reset process", u.Email))
|
||||
return
|
||||
}
|
||||
|
||||
s.Context.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
appURL := s.Context.GetAppURL(fmt.Sprintf("auth/reset/%s", token))
|
||||
appURL := ctx.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) {
|
||||
// ResetPassword stores the newly chosen password for the user.
|
||||
func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
method := "user.ForgotUserPassword"
|
||||
s := domain.NewContext(h.Runtime, r)
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
token := request.Param("token")
|
||||
token := request.Param(r, "token")
|
||||
if len(token) == 0 {
|
||||
response.WriteMissingDataError(w, method, "missing token")
|
||||
return
|
||||
|
@ -586,31 +604,30 @@ func (h *Handler) ResetUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
newPassword := string(body)
|
||||
|
||||
s.Context.Transaction, err = h.Runtime.Db.Beginx()
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := GetByToken(token)
|
||||
u, err := h.Store.User.GetByToken(ctx, token)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
user.Salt = secrets.GenerateSalt()
|
||||
u.Salt = secrets.GenerateSalt()
|
||||
|
||||
err = UpdateUserPassword(s, u.RefID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt))
|
||||
err = h.Store.User.UpdateUserPassword(ctx, u.RefID, u.Salt, secrets.GeneratePassword(newPassword, u.Salt))
|
||||
if err != nil {
|
||||
s.Context.Transaction.Rollback()
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
eventing.Record(s, eventing.EventTypeUserPasswordReset)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeUserPasswordReset)
|
||||
|
||||
s.Context.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue