2017-07-24 16:24:21 +01:00
// 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
2017-07-26 10:50:26 +01:00
package mysql
2017-07-24 16:24:21 +01:00
import (
"database/sql"
"fmt"
2018-02-28 14:55:36 +00:00
"strconv"
2017-07-24 16:24:21 +01:00
"strings"
"time"
2017-07-26 10:50:26 +01:00
"github.com/documize/community/core/env"
2017-07-24 16:24:21 +01:00
"github.com/documize/community/domain"
2017-07-26 10:50:26 +01:00
"github.com/documize/community/model/user"
2017-10-05 15:02:39 -04:00
"github.com/jmoiron/sqlx"
2017-07-24 16:24:21 +01:00
"github.com/pkg/errors"
)
2017-07-26 10:50:26 +01:00
// Scope provides data access to MySQL.
type Scope struct {
Runtime * env . Runtime
}
2017-07-24 16:24:21 +01:00
// Add adds the given user record to the user table.
2017-07-26 10:50:26 +01:00
func ( s Scope ) Add ( ctx domain . RequestContext , u user . User ) ( err error ) {
2017-07-24 16:24:21 +01:00
u . Created = time . Now ( ) . UTC ( )
u . Revised = time . Now ( ) . UTC ( )
2018-03-23 11:52:19 +00:00
_ , err = ctx . Transaction . Exec ( "INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, lastversion, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" ,
2018-03-22 17:29:59 +00:00
u . RefID , u . Firstname , u . Lastname , strings . ToLower ( u . Email ) , u . Initials , u . Password , u . Salt , "" , u . LastVersion , u . Created , u . Revised )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user insert" )
}
return
}
// Get returns the user record for the given id.
2017-07-26 10:50:26 +01:00
func ( s Scope ) Get ( ctx domain . RequestContext , id string ) ( u user . User , err error ) {
2018-03-22 17:29:59 +00:00
err = s . Runtime . Db . Get ( & u , "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE refid=?" , id )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "unable to execute select for user %s" , id ) )
}
return
}
// GetByDomain matches user by email and domain.
2017-07-26 10:50:26 +01:00
func ( s Scope ) GetByDomain ( ctx domain . RequestContext , domain , email string ) ( u user . User , err error ) {
2017-07-24 16:24:21 +01:00
email = strings . TrimSpace ( strings . ToLower ( email ) )
2018-03-22 17:29:59 +00:00
err = s . Runtime . Db . Get ( & u , "SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?" ,
2017-09-25 14:37:11 +01:00
email , domain )
2017-07-24 16:24:21 +01:00
if err != nil && err != sql . ErrNoRows {
err = errors . Wrap ( err , fmt . Sprintf ( "Unable to execute GetUserByDomain %s %s" , domain , email ) )
}
return
}
// GetByEmail returns a single row match on email.
2017-07-26 10:50:26 +01:00
func ( s Scope ) GetByEmail ( ctx domain . RequestContext , email string ) ( u user . User , err error ) {
2017-07-24 16:24:21 +01:00
email = strings . TrimSpace ( strings . ToLower ( email ) )
2018-03-22 17:29:59 +00:00
err = s . Runtime . Db . Get ( & u , "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE TRIM(LOWER(email))=?" , email )
2017-07-24 16:24:21 +01:00
if err != nil && err != sql . ErrNoRows {
err = errors . Wrap ( err , fmt . Sprintf ( "execute select user by email %s" , email ) )
}
return
}
// GetByToken returns a user record given a reset token value.
2017-07-26 10:50:26 +01:00
func ( s Scope ) GetByToken ( ctx domain . RequestContext , token string ) ( u user . User , err error ) {
2018-03-22 17:29:59 +00:00
err = s . Runtime . Db . Get ( & u , "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE reset=?" , token )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user select by token %s" , token ) )
}
return
}
// GetBySerial is used to retrieve a user via their temporary password salt value!
// This occurs when we you share a folder with a new user and they have to complete
// the onboarding process.
2017-07-26 10:50:26 +01:00
func ( s Scope ) GetBySerial ( ctx domain . RequestContext , serial string ) ( u user . User , err error ) {
2018-03-22 17:29:59 +00:00
err = s . Runtime . Db . Get ( & u , "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE salt=?" , serial )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user select by serial %s" , serial ) )
}
return
}
// GetActiveUsersForOrganization returns a slice containing of active user records for the organization
// identified in the Persister.
2017-07-26 10:50:26 +01:00
func ( s Scope ) GetActiveUsersForOrganization ( ctx domain . RequestContext ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2017-07-24 16:24:21 +01:00
err = s . Runtime . Db . Select ( & u ,
2018-03-22 17:29:59 +00:00
` SELECT u . id , u . refid , u . firstname , u . lastname , u . email , u . initials , u . password , u . salt , u . reset , u . lastversion , u . created , u . revised ,
2018-05-10 15:14:50 +01:00
u . global , a . active , a . editor , ` +"a. ` admin ` "+ ` , a . users as viewusers , a . analytics
2017-10-03 17:52:03 -04:00
FROM user u , account a
WHERE u . refid = a . userid AND a . orgid = ? AND a . active = 1
ORDER BY u . firstname , u . lastname ` ,
2017-07-26 10:50:26 +01:00
ctx . OrgID )
2017-07-24 16:24:21 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-01 19:14:27 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( "get active users by org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
// identified in the Persister.
2018-03-01 19:14:27 +00:00
func ( s Scope ) GetUsersForOrganization ( ctx domain . RequestContext , filter string ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-03-01 19:14:27 +00:00
filter = strings . TrimSpace ( strings . ToLower ( filter ) )
likeQuery := ""
if len ( filter ) > 0 {
likeQuery = " AND (LOWER(u.firstname) LIKE '%" + filter + "%' OR LOWER(u.lastname) LIKE '%" + filter + "%' OR LOWER(u.email) LIKE '%" + filter + "%') "
}
2017-07-24 16:24:21 +01:00
err = s . Runtime . Db . Select ( & u ,
2018-03-22 17:29:59 +00:00
` SELECT u . id , u . refid , u . firstname , u . lastname , u . email , u . initials , u . password , u . salt , u . reset , u . lastversion , u . created , u . revised ,
2018-05-10 15:14:50 +01:00
u . global , a . active , a . editor ` +"a. ` admin ` "+ ` , a . users as viewusers , a . analytics
2017-10-03 17:52:03 -04:00
FROM user u , account a
2018-03-01 19:14:27 +00:00
WHERE u . refid = a . userid AND a . orgid = ? ` + likeQuery +
` ORDER BY u.firstname, u.lastname LIMIT 100 ` , ctx . OrgID )
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-01 19:14:27 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( " get users for org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
2017-10-05 15:02:39 -04:00
// GetSpaceUsers returns a slice containing all user records for given space.
2017-09-18 13:02:15 +01:00
func ( s Scope ) GetSpaceUsers ( ctx domain . RequestContext , spaceID string ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2017-09-18 13:02:15 +01:00
err = s . Runtime . Db . Select ( & u , `
2018-03-22 17:29:59 +00:00
SELECT u . id , u . refid , u . firstname , u . lastname , u . email , u . initials , u . password , u . salt , u . reset , u . created , u . lastversion , u . revised , u . global ,
2018-05-10 15:14:50 +01:00
a . active , a . users AS viewusers , a . editor , ` +"a. ` admin ` "+ ` , a . analytics
2017-07-24 16:24:21 +01:00
FROM user u , account a
2017-09-18 13:02:15 +01:00
WHERE a . orgid = ? AND u . refid = a . userid AND a . active = 1 AND u . refid IN (
SELECT whoid from permission WHERE orgid = ? AND who = ' user ' AND scope = ' object ' AND location = ' space ' AND refid = ? UNION ALL
SELECT r . userid from rolemember r LEFT JOIN permission p ON p . whoid = r . roleid WHERE p . orgid = ? AND p . who = ' role ' AND p . scope = ' object ' AND p . location = ' space ' AND p . refid = ?
)
2017-10-05 15:02:39 -04:00
ORDER BY u . firstname , u . lastname
2017-09-18 13:02:15 +01:00
` , ctx . OrgID , ctx . OrgID , spaceID , ctx . OrgID , spaceID )
2017-07-24 16:24:21 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-08 10:51:12 +00:00
err = nil
}
2017-07-24 16:24:21 +01:00
if err != nil {
2017-07-26 10:50:26 +01:00
err = errors . Wrap ( err , fmt . Sprintf ( "get space users for org %s" , ctx . OrgID ) )
2017-07-24 16:24:21 +01:00
}
return
}
2017-10-05 15:02:39 -04:00
// GetUsersForSpaces returns users with access to specified spaces.
func ( s Scope ) GetUsersForSpaces ( ctx domain . RequestContext , spaces [ ] string ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2017-10-09 10:56:59 -04:00
if len ( spaces ) == 0 {
return
}
2017-10-05 15:02:39 -04:00
query , args , err := sqlx . In ( `
2018-03-22 17:29:59 +00:00
SELECT u . id , u . refid , u . firstname , u . lastname , u . email , u . initials , u . password , u . salt , u . reset , u . lastversion , u . created , u . revised , u . global ,
2018-05-10 15:14:50 +01:00
a . active , a . users AS viewusers , a . editor , ` +"a. ` admin ` "+ ` , a . analytics
2017-10-05 15:02:39 -04:00
FROM user u , account a
WHERE a . orgid = ? AND u . refid = a . userid AND a . active = 1 AND u . refid IN (
SELECT whoid from permission WHERE orgid = ? AND who = ' user ' AND scope = ' object ' AND location = ' space ' AND refid IN ( ? ) UNION ALL
2018-03-22 17:29:59 +00:00
SELECT r . userid from rolemember r LEFT JOIN permission p ON p . whoid = r . roleid WHERE p . orgid = ? AND p . who = ' role ' AND p . scope = ' object ' AND p . location = ' space ' AND p . refid IN ( ? )
2017-10-05 15:02:39 -04:00
)
ORDER BY u . firstname , u . lastname
` , ctx . OrgID , ctx . OrgID , spaces , ctx . OrgID , spaces )
query = s . Runtime . Db . Rebind ( query )
err = s . Runtime . Db . Select ( & u , query , args ... )
2017-08-17 13:47:43 +01:00
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-03-08 10:51:12 +00:00
err = nil
}
2017-08-17 13:47:43 +01:00
if err != nil {
2017-10-05 15:02:39 -04:00
err = errors . Wrap ( err , fmt . Sprintf ( "get users for spaces for user %s" , ctx . UserID ) )
2017-08-17 13:47:43 +01:00
}
return
}
2017-07-24 16:24:21 +01:00
// UpdateUser updates the user table using the given replacement user record.
2017-07-26 10:50:26 +01:00
func ( s Scope ) UpdateUser ( ctx domain . RequestContext , u user . User ) ( err error ) {
2017-07-24 16:24:21 +01:00
u . Revised = time . Now ( ) . UTC ( )
u . Email = strings . ToLower ( u . Email )
2017-09-25 14:37:11 +01:00
_ , err = ctx . Transaction . NamedExec (
2018-03-22 17:29:59 +00:00
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials, lastversion=:lastversion WHERE refid=:refid" , & u )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "execute user update %s" , u . RefID ) )
}
return
}
// UpdateUserPassword updates a user record with new password and salt values.
2017-07-26 10:50:26 +01:00
func ( s Scope ) UpdateUserPassword ( ctx domain . RequestContext , userID , salt , password string ) ( err error ) {
2017-09-25 14:37:11 +01:00
_ , err = ctx . Transaction . Exec ( "UPDATE user SET salt=?, password=?, reset='' WHERE refid=?" ,
salt , password , userID )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user update" )
}
return
}
// DeactiveUser deletes the account record for the given userID and persister.Context.OrgID.
2017-07-26 10:50:26 +01:00
func ( s Scope ) DeactiveUser ( ctx domain . RequestContext , userID string ) ( err error ) {
2017-09-25 14:37:11 +01:00
_ , err = ctx . Transaction . Exec ( "DELETE FROM account WHERE userid=? and orgid=?" , userID , ctx . OrgID )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute user deactivation" )
}
return
}
// ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email.
2017-07-26 10:50:26 +01:00
func ( s Scope ) ForgotUserPassword ( ctx domain . RequestContext , email , token string ) ( err error ) {
2017-09-25 14:37:11 +01:00
_ , err = ctx . Transaction . Exec ( "UPDATE user SET reset=?, password='' WHERE LOWER(email)=?" , token , strings . ToLower ( email ) )
2017-07-24 16:24:21 +01:00
if err != nil {
err = errors . Wrap ( err , "execute password reset" )
}
return
}
// CountActiveUsers returns the number of active users in the system.
2017-08-04 13:17:09 +01:00
func ( s Scope ) CountActiveUsers ( ) ( c int ) {
2017-07-24 16:24:21 +01:00
row := s . Runtime . Db . QueryRow ( "SELECT count(*) FROM user u WHERE u.refid IN (SELECT userid FROM account WHERE active=1)" )
err := row . Scan ( & c )
if err == sql . ErrNoRows {
return 0
}
if err != nil && err != sql . ErrNoRows {
s . Runtime . Log . Error ( "CountActiveUsers" , err )
return 0
}
return
}
2018-02-28 14:55:36 +00:00
// MatchUsers returns users that have match to either firstname, lastname or email.
func ( s Scope ) MatchUsers ( ctx domain . RequestContext , text string , maxMatches int ) ( u [ ] user . User , err error ) {
2018-04-05 14:22:23 +01:00
u = [ ] user . User { }
2018-02-28 14:55:36 +00:00
text = strings . TrimSpace ( strings . ToLower ( text ) )
likeQuery := ""
if len ( text ) > 0 {
likeQuery = " AND (LOWER(firstname) LIKE '%" + text + "%' OR LOWER(lastname) LIKE '%" + text + "%' OR LOWER(email) LIKE '%" + text + "%') "
}
err = s . Runtime . Db . Select ( & u ,
2018-03-22 17:29:59 +00:00
` SELECT u . id , u . refid , u . firstname , u . lastname , u . email , u . initials , u . password , u . salt , u . reset , u . lastversion , u . created , u . revised ,
2018-05-10 15:14:50 +01:00
u . global , a . active , a . editor , ` +"a. ` admin ` "+ ` , a . users as viewusers , a . analytics
2018-02-28 14:55:36 +00:00
FROM user u , account a
WHERE a . orgid = ? AND u . refid = a . userid AND a . active = 1 ` + likeQuery +
` ORDER BY u.firstname,u.lastname LIMIT ` + strconv . Itoa ( maxMatches ) ,
ctx . OrgID )
2018-04-05 14:22:23 +01:00
if err == sql . ErrNoRows {
2018-02-28 14:55:36 +00:00
err = nil
}
if err != nil {
err = errors . Wrap ( err , fmt . Sprintf ( "matching users for org %s" , ctx . OrgID ) )
}
return
}