1
0
Fork 0
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:
Harvey Kandola 2017-07-26 20:03:23 +01:00
parent 72b14def6d
commit d90b3249c3
44 changed files with 5276 additions and 336 deletions

View 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)
}

View 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)
}

View file

@ -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
View 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
}

View file

@ -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"`
}
*/

View file

@ -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
}

View 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
View 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)
}

View file

@ -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
View 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
View 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
}

View 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
View 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
View 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>
*/

View 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
}

View file

@ -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)
}

View file

View 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)
}
*/