mirror of
https://github.com/documize/community.git
synced 2025-07-27 09:09:44 +02:00
Merge pull request #185 from documize/app-subscription
App subscription
This commit is contained in:
commit
cb9fd0940d
61 changed files with 1715 additions and 1195 deletions
|
@ -58,9 +58,9 @@ Space view.
|
||||||
|
|
||||||
## Latest version
|
## Latest version
|
||||||
|
|
||||||
[Community edition: v1.72.1](https://github.com/documize/community/releases)
|
[Community edition: v1.73.0](https://github.com/documize/community/releases)
|
||||||
|
|
||||||
[Enterprise edition: v1.74.1](https://documize.com/downloads)
|
[Enterprise edition: v1.75.0](https://documize.com/downloads)
|
||||||
|
|
||||||
## OS support
|
## OS support
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ Documize supports the following (evergreen) browsers:
|
||||||
Documize is built with the following technologies:
|
Documize is built with the following technologies:
|
||||||
|
|
||||||
- EmberJS (v3.1.2)
|
- EmberJS (v3.1.2)
|
||||||
- Go (v1.11.1)
|
- Go (v1.11.2)
|
||||||
|
|
||||||
## Authentication options
|
## Authentication options
|
||||||
|
|
||||||
|
|
|
@ -32,15 +32,6 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out database specific scripts.
|
|
||||||
dbTypeScripts := SpecificScripts(runtime, scripts)
|
|
||||||
if len(dbTypeScripts) == 0 {
|
|
||||||
runtime.Log.Info(fmt.Sprintf("Database: unable to load scripts for database type %s", runtime.StoreProvider.Type()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.Log.Info(fmt.Sprintf("Database: loaded %d SQL scripts for provider %s", len(dbTypeScripts), runtime.StoreProvider.Type()))
|
|
||||||
|
|
||||||
// Get current database version.
|
// Get current database version.
|
||||||
currentVersion := 0
|
currentVersion := 0
|
||||||
if existingDB {
|
if existingDB {
|
||||||
|
@ -53,6 +44,15 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
||||||
runtime.Log.Info(fmt.Sprintf("Database: current version number is %d", currentVersion))
|
runtime.Log.Info(fmt.Sprintf("Database: current version number is %d", currentVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter out database specific scripts.
|
||||||
|
dbTypeScripts := SpecificScripts(runtime, scripts)
|
||||||
|
if len(dbTypeScripts) == 0 {
|
||||||
|
runtime.Log.Info(fmt.Sprintf("Database: unable to load scripts for database type %s", runtime.StoreProvider.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Log.Info(fmt.Sprintf("Database: loaded %d SQL scripts for provider %s", len(dbTypeScripts), runtime.StoreProvider.Type()))
|
||||||
|
|
||||||
// Make a list of scripts to execute based upon current database state.
|
// Make a list of scripts to execute based upon current database state.
|
||||||
toProcess := []Script{}
|
toProcess := []Script{}
|
||||||
for _, s := range dbTypeScripts {
|
for _, s := range dbTypeScripts {
|
||||||
|
@ -90,52 +90,6 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
// New style schema
|
|
||||||
// if existingDB {
|
|
||||||
// amLeader, err = Lock(runtime, len(toProcess))
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Error("Database: failed to lock existing database for processing", err)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // New installation hopes that you are only spinning up one instance of Documize.
|
|
||||||
// // Assumption: nobody will perform the intial setup in a clustered environment.
|
|
||||||
// amLeader = true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tx, err := runtime.Db.Beginx()
|
|
||||||
// if err != nil {
|
|
||||||
// return Unlock(runtime, tx, err, amLeader)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // If currently running process is database leader then we perform upgrade.
|
|
||||||
// if amLeader {
|
|
||||||
// runtime.Log.Info(fmt.Sprintf("Database: %d SQL scripts to process", len(toProcess)))
|
|
||||||
|
|
||||||
// err = runScripts(runtime, tx, toProcess)
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Error("Database: error processing SQL script", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Unlock(runtime, tx, err, amLeader)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // If currently running process is a slave instance then we wait for migration to complete.
|
|
||||||
// targetVersion := toProcess[len(toProcess)-1].Version
|
|
||||||
|
|
||||||
// for targetVersion != currentVersion {
|
|
||||||
// time.Sleep(time.Second)
|
|
||||||
// runtime.Log.Info("Database: slave instance polling for upgrade process completion")
|
|
||||||
// tx.Rollback()
|
|
||||||
|
|
||||||
// // Get database version and check again.
|
|
||||||
// currentVersion, err = CurrentVersion(runtime)
|
|
||||||
// if err != nil {
|
|
||||||
// return Unlock(runtime, tx, err, amLeader)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Unlock(runtime, tx, nil, amLeader)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run SQL scripts to instal or upgrade this database.
|
// Run SQL scripts to instal or upgrade this database.
|
||||||
|
|
6
core/database/scripts/mysql/db_00026.sql
Normal file
6
core/database/scripts/mysql/db_00026.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* community edition */
|
||||||
|
|
||||||
|
-- add subscription
|
||||||
|
ALTER TABLE dmz_org ADD COLUMN `c_sub` JSON NULL AFTER `c_authconfig`;
|
||||||
|
|
||||||
|
-- deprecations
|
6
core/database/scripts/postgresql/db_00002.sql
Normal file
6
core/database/scripts/postgresql/db_00002.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* community edition */
|
||||||
|
|
||||||
|
-- add subscription
|
||||||
|
ALTER TABLE dmz_org ADD COLUMN c_sub JSON NULL;
|
||||||
|
|
||||||
|
-- deprecations
|
27
core/env/flags.go
vendored
27
core/env/flags.go
vendored
|
@ -25,12 +25,13 @@ import (
|
||||||
type Flags struct {
|
type Flags struct {
|
||||||
DBConn string // database connection string
|
DBConn string // database connection string
|
||||||
Salt string // the salt string used to encode JWT tokens
|
Salt string // the salt string used to encode JWT tokens
|
||||||
DBType string // (optional) database type
|
DBType string // database type
|
||||||
SSLCertFile string // (optional) name of SSL certificate PEM file
|
SSLCertFile string // (optional) name of SSL certificate PEM file
|
||||||
SSLKeyFile string // (optional) name of SSL key PEM file
|
SSLKeyFile string // (optional) name of SSL key PEM file
|
||||||
HTTPPort string // (optional) HTTP or HTTPS port
|
HTTPPort string // (optional) HTTP or HTTPS port
|
||||||
ForceHTTPPort2SSL string // (optional) HTTP that should be redirected to HTTPS
|
ForceHTTPPort2SSL string // (optional) HTTP that should be redirected to HTTPS
|
||||||
SiteMode string // (optional) if 1 then serve offline web page
|
SiteMode string // (optional) if 1 then serve offline web page
|
||||||
|
Location string // reserved
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSLEnabled returns true if both cert and key were provided at runtime.
|
// SSLEnabled returns true if both cert and key were provided at runtime.
|
||||||
|
@ -71,8 +72,9 @@ var flagList progFlags
|
||||||
var loadMutex sync.Mutex
|
var loadMutex sync.Mutex
|
||||||
|
|
||||||
// ParseFlags loads command line and OS environment variables required by the program to function.
|
// ParseFlags loads command line and OS environment variables required by the program to function.
|
||||||
func ParseFlags() (f Flags) {
|
func ParseFlags() (f Flags, ok bool) {
|
||||||
var dbConn, dbType, jwtKey, siteMode, port, certFile, keyFile, forcePort2SSL string
|
ok = true
|
||||||
|
var dbConn, dbType, jwtKey, siteMode, port, certFile, keyFile, forcePort2SSL, location string
|
||||||
|
|
||||||
register(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
register(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
||||||
register(&certFile, "cert", false, "the cert.pem file used for https")
|
register(&certFile, "cert", false, "the cert.pem file used for https")
|
||||||
|
@ -82,8 +84,11 @@ func ParseFlags() (f Flags) {
|
||||||
register(&siteMode, "offline", false, "set to '1' for OFFLINE mode")
|
register(&siteMode, "offline", false, "set to '1' for OFFLINE mode")
|
||||||
register(&dbType, "dbtype", true, "specify the database provider: mysql|percona|mariadb|postgresql")
|
register(&dbType, "dbtype", true, "specify the database provider: mysql|percona|mariadb|postgresql")
|
||||||
register(&dbConn, "db", true, `'database specific connection string for example "user:password@tcp(localhost:3306)/dbname"`)
|
register(&dbConn, "db", true, `'database specific connection string for example "user:password@tcp(localhost:3306)/dbname"`)
|
||||||
|
register(&location, "location", false, `reserved`)
|
||||||
|
|
||||||
parse("db")
|
if !parse("db") {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
f.DBConn = dbConn
|
f.DBConn = dbConn
|
||||||
f.ForceHTTPPort2SSL = forcePort2SSL
|
f.ForceHTTPPort2SSL = forcePort2SSL
|
||||||
|
@ -94,7 +99,13 @@ func ParseFlags() (f Flags) {
|
||||||
f.SSLKeyFile = keyFile
|
f.SSLKeyFile = keyFile
|
||||||
f.DBType = strings.ToLower(dbType)
|
f.DBType = strings.ToLower(dbType)
|
||||||
|
|
||||||
return f
|
// reserved
|
||||||
|
if len(location) == 0 {
|
||||||
|
location = "selfhost"
|
||||||
|
}
|
||||||
|
f.Location = strings.ToLower(location)
|
||||||
|
|
||||||
|
return f, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// register prepares flag for subsequent parsing
|
// register prepares flag for subsequent parsing
|
||||||
|
@ -116,7 +127,7 @@ func register(target *string, name string, required bool, usage string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse loads flags from OS environment and command line switches
|
// parse loads flags from OS environment and command line switches
|
||||||
func parse(doFirst string) {
|
func parse(doFirst string) (ok bool) {
|
||||||
loadMutex.Lock()
|
loadMutex.Lock()
|
||||||
defer loadMutex.Unlock()
|
defer loadMutex.Unlock()
|
||||||
|
|
||||||
|
@ -141,10 +152,12 @@ func parse(doFirst string) {
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr)
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
73
core/env/product.go
vendored
73
core/env/product.go
vendored
|
@ -1,73 +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 env
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProdInfo describes a product
|
|
||||||
type ProdInfo struct {
|
|
||||||
Edition string
|
|
||||||
Title string
|
|
||||||
Version string
|
|
||||||
Major string
|
|
||||||
Minor string
|
|
||||||
Patch string
|
|
||||||
Revision int
|
|
||||||
License License
|
|
||||||
}
|
|
||||||
|
|
||||||
// License holds details of product license.
|
|
||||||
type License struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Edition string `json:"edition"`
|
|
||||||
Package string `json:"package"`
|
|
||||||
Plan string `json:"plan"`
|
|
||||||
Start time.Time `json:"start"`
|
|
||||||
End time.Time `json:"end"`
|
|
||||||
Seats int `json:"seats"`
|
|
||||||
Trial bool `json:"trial"`
|
|
||||||
Valid bool `json:"valid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines if we have a license.
|
|
||||||
func (l *License) IsEmpty() bool {
|
|
||||||
return l.Seats == 0 && len(l.Name) == 0 && len(l.Email) == 0 && l.Start.Year() == 1 && l.End.Year() == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns formatted message stating if license is empty/populated and invalid/valid.
|
|
||||||
func (l *License) Status() string {
|
|
||||||
lp := "populated"
|
|
||||||
if l.IsEmpty() {
|
|
||||||
lp = "empty"
|
|
||||||
}
|
|
||||||
lv := "invalid"
|
|
||||||
if l.Valid {
|
|
||||||
lv = "valid"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("License is %s and %s", lp, lv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid returns if license is valid
|
|
||||||
func (l *License) IsValid() bool {
|
|
||||||
return l.Valid == true
|
|
||||||
}
|
|
||||||
|
|
||||||
// LicenseData holds encrypted data and is unpacked into License.
|
|
||||||
type LicenseData struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
}
|
|
11
core/env/runtime.go
vendored
11
core/env/runtime.go
vendored
|
@ -13,6 +13,7 @@
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/documize/community/domain"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ type Runtime struct {
|
||||||
Db *sqlx.DB
|
Db *sqlx.DB
|
||||||
StoreProvider StoreProvider
|
StoreProvider StoreProvider
|
||||||
Log Logger
|
Log Logger
|
||||||
Product ProdInfo
|
Product domain.Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,11 +40,3 @@ const (
|
||||||
// SiteModeBadDB redirects to db-error.html page
|
// SiteModeBadDB redirects to db-error.html page
|
||||||
SiteModeBadDB = "3"
|
SiteModeBadDB = "3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// CommunityEdition is AGPL product variant
|
|
||||||
CommunityEdition = "Community"
|
|
||||||
|
|
||||||
// EnterpriseEdition is commercial licensed product variant
|
|
||||||
EnterpriseEdition = "Enterprise"
|
|
||||||
)
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ package uniqueid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/core/uniqueid/xid"
|
"github.com/documize/community/core/uniqueid/xid"
|
||||||
"github.com/documize/community/core/uniqueid/xid16"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate creates a randomly generated string suitable for use as part of an URI.
|
// Generate creates a randomly generated string suitable for use as part of an URI.
|
||||||
|
@ -22,16 +21,3 @@ import (
|
||||||
func Generate() string {
|
func Generate() string {
|
||||||
return xid.New().String()
|
return xid.New().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate16 creates a randomly generated 16 character length string suitable for use as part of an URI.
|
|
||||||
// It returns a string that is always 16 characters long.
|
|
||||||
func Generate16() string {
|
|
||||||
return xid16.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// beqassjmvbajrivsc0eg
|
|
||||||
// beqat1bmvbajrivsc0f0
|
|
||||||
|
|
||||||
// beqat1bmvbajrivsc1ag
|
|
||||||
// beqat1bmvbajrivsc1g0
|
|
||||||
// beqat1bmvbajrivsc1ug
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ func StripAuthSecrets(r *env.Runtime, provider, config string) string {
|
||||||
switch provider {
|
switch provider {
|
||||||
case auth.AuthProviderDocumize:
|
case auth.AuthProviderDocumize:
|
||||||
return config
|
return config
|
||||||
|
|
||||||
case auth.AuthProviderKeycloak:
|
case auth.AuthProviderKeycloak:
|
||||||
c := auth.KeycloakConfig{}
|
c := auth.KeycloakConfig{}
|
||||||
err := json.Unmarshal([]byte(config), &c)
|
err := json.Unmarshal([]byte(config), &c)
|
||||||
|
@ -41,6 +42,7 @@ func StripAuthSecrets(r *env.Runtime, provider, config string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(j)
|
return string(j)
|
||||||
|
|
||||||
case auth.AuthProviderLDAP:
|
case auth.AuthProviderLDAP:
|
||||||
c := auth.LDAPConfig{}
|
c := auth.LDAPConfig{}
|
||||||
err := json.Unmarshal([]byte(config), &c)
|
err := json.Unmarshal([]byte(config), &c)
|
||||||
|
|
|
@ -21,6 +21,8 @@ package backup
|
||||||
// the file is deleted at the end of the process.
|
// the file is deleted at the end of the process.
|
||||||
//
|
//
|
||||||
// The backup file contains a manifest file that describes the backup.
|
// The backup file contains a manifest file that describes the backup.
|
||||||
|
//
|
||||||
|
// TODO: explore writing straight to HTTP response via https://github.com/mholt/archiver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
@ -266,7 +268,10 @@ func (b backerHandler) dmzConfig(files *[]backupItem) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
*files = append(*files, backupItem{Filename: "dmz_config.json", Content: content})
|
|
||||||
|
if b.Spec.SystemBackup() {
|
||||||
|
*files = append(*files, backupItem{Filename: "dmz_config.json", Content: content})
|
||||||
|
}
|
||||||
|
|
||||||
w := ""
|
w := ""
|
||||||
if !b.Spec.SystemBackup() {
|
if !b.Spec.SystemBackup() {
|
||||||
|
|
|
@ -136,9 +136,11 @@ func (r *restoreHandler) PerformRestore(b []byte, l int64) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config.
|
// Config.
|
||||||
err = r.dmzConfig()
|
if r.Context.GlobalAdmin {
|
||||||
if err != nil {
|
err = r.dmzConfig()
|
||||||
return
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audit Log.
|
// Audit Log.
|
||||||
|
@ -449,6 +451,11 @@ func (r *restoreHandler) dmzConfig() (err error) {
|
||||||
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
|
r.Runtime.Log.Info(fmt.Sprintf("Extracted %s", filename))
|
||||||
|
|
||||||
for i := range c {
|
for i := range c {
|
||||||
|
// We skip database schema version setting as this varies
|
||||||
|
// between database providers (e.g. MySQL v26, PostgreSQL v2).
|
||||||
|
if strings.ToUpper(c[i].ConfigKey) == "META" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
err = r.Store.Setting.Set(c[i].ConfigKey, c[i].ConfigValue)
|
err = r.Store.Setting.Set(c[i].ConfigKey, c[i].ConfigValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, c[i].ConfigKey))
|
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, c[i].ConfigKey))
|
||||||
|
@ -1644,8 +1651,7 @@ func (r *restoreHandler) dmzUser() (err error) {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("unable to check email %s", u[i].Email))
|
err = errors.Wrap(err, fmt.Sprintf("unable to check email %s", u[i].Email))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Existing userID from database overrides all incoming userID values
|
// Existing userID from database overrides all incoming userID values by using remapUser().
|
||||||
// by using remapUser().
|
|
||||||
if len(userID) > 0 {
|
if len(userID) > 0 {
|
||||||
r.MapUserID[u[i].RefID] = userID
|
r.MapUserID[u[i].RefID] = userID
|
||||||
insert = false
|
insert = false
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "block.add"
|
method := "block.add"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ import (
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestContext provides per request scoped values required
|
// RequestContext provides per request scoped values required for HTTP handlers.
|
||||||
// by HTTP handlers.
|
|
||||||
type RequestContext struct {
|
type RequestContext struct {
|
||||||
AllowAnonymousAccess bool
|
AllowAnonymousAccess bool
|
||||||
Authenticated bool
|
Authenticated bool
|
||||||
|
@ -36,14 +35,13 @@ type RequestContext struct {
|
||||||
Expires time.Time
|
Expires time.Time
|
||||||
Fullname string
|
Fullname string
|
||||||
Transaction *sqlx.Tx
|
Transaction *sqlx.Tx
|
||||||
AppVersion string
|
Administrator bool
|
||||||
|
Analytics bool
|
||||||
Administrator bool
|
Active bool
|
||||||
Analytics bool
|
Editor bool
|
||||||
Active bool
|
GlobalAdmin bool
|
||||||
Editor bool
|
ViewUsers bool
|
||||||
GlobalAdmin bool
|
Subscription Subscription
|
||||||
ViewUsers bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetAppURL returns full HTTP url for the app
|
//GetAppURL returns full HTTP url for the app
|
||||||
|
|
|
@ -174,7 +174,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
||||||
documentID := uniqueid.Generate()
|
documentID := uniqueid.Generate()
|
||||||
document.RefID = documentID
|
document.RefID = documentID
|
||||||
|
|
||||||
if r.Product.Edition == env.CommunityEdition {
|
if r.Product.Edition == domain.CommunityEdition {
|
||||||
document.Lifecycle = workflow.LifecycleLive
|
document.Lifecycle = workflow.LifecycleLive
|
||||||
} else {
|
} else {
|
||||||
document.Lifecycle = sp.Lifecycle
|
document.Lifecycle = sp.Lifecycle
|
||||||
|
|
|
@ -83,7 +83,6 @@ func (store *LocalStorageProvider) Convert(params api.ConversionJobRequest) (fil
|
||||||
defer func() { os.RemoveAll(inputFolder) }()
|
defer func() { os.RemoveAll(inputFolder) }()
|
||||||
|
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
|
|
||||||
if v.Size() > 0 && !strings.HasPrefix(v.Name(), ".") && v.Mode().IsRegular() {
|
if v.Size() > 0 && !strings.HasPrefix(v.Name(), ".") && v.Mode().IsRegular() {
|
||||||
filename = inputFolder + v.Name()
|
filename = inputFolder + v.Name()
|
||||||
fileData, err := ioutil.ReadFile(filename)
|
fileData, err := ioutil.ReadFile(filename)
|
||||||
|
@ -100,8 +99,6 @@ func (store *LocalStorageProvider) Convert(params api.ConversionJobRequest) (fil
|
||||||
fileRequest.LicenseKey = params.LicenseKey
|
fileRequest.LicenseKey = params.LicenseKey
|
||||||
fileRequest.LicenseSignature = params.LicenseSignature
|
fileRequest.LicenseSignature = params.LicenseSignature
|
||||||
fileRequest.ServiceEndpoint = params.ServiceEndpoint
|
fileRequest.ServiceEndpoint = params.ServiceEndpoint
|
||||||
//fileRequest.Job = params.OrgID + string(os.PathSeparator) + params.Job
|
|
||||||
//fileRequest.OrgID = params.OrgID
|
|
||||||
|
|
||||||
bits := strings.Split(filename, ".")
|
bits := strings.Split(filename, ".")
|
||||||
xtn := strings.ToLower(bits[len(bits)-1])
|
xtn := strings.ToLower(bits[len(bits)-1])
|
||||||
|
|
|
@ -60,10 +60,9 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
|
||||||
data.Version = h.Runtime.Product.Version
|
data.Version = h.Runtime.Product.Version
|
||||||
data.Revision = h.Runtime.Product.Revision
|
data.Revision = h.Runtime.Product.Revision
|
||||||
data.Edition = h.Runtime.Product.Edition
|
data.Edition = h.Runtime.Product.Edition
|
||||||
data.Valid = h.Runtime.Product.License.Valid
|
|
||||||
data.ConversionEndpoint = org.ConversionEndpoint
|
data.ConversionEndpoint = org.ConversionEndpoint
|
||||||
data.License = h.Runtime.Product.License
|
|
||||||
data.Storage = h.Runtime.StoreProvider.Type()
|
data.Storage = h.Runtime.StoreProvider.Type()
|
||||||
|
data.Location = h.Runtime.Flags.Location // reserved
|
||||||
|
|
||||||
// Strip secrets
|
// Strip secrets
|
||||||
data.AuthConfig = auth.StripAuthSecrets(h.Runtime, org.AuthProvider, org.AuthConfig)
|
data.AuthConfig = auth.StripAuthSecrets(h.Runtime, org.AuthProvider, org.AuthConfig)
|
||||||
|
|
|
@ -31,10 +31,10 @@ type Store struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOrganization inserts the passed organization record into the organization table.
|
// AddOrganization inserts the passed organization record into the organization table.
|
||||||
func (s Store) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
|
func (s Store) AddOrganization(ctx domain.RequestContext, o org.Organization) (err error) {
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_sub, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
||||||
org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.Domain),
|
o.RefID, o.Company, o.Title, o.Message, strings.ToLower(o.Domain),
|
||||||
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised)
|
strings.ToLower(o.Email), o.AllowAnonymousAccess, o.Serial, o.MaxTags, o.Subscription, o.Created, o.Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "unable to execute insert for org")
|
err = errors.Wrap(err, "unable to execute insert for org")
|
||||||
|
@ -49,8 +49,8 @@ func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Or
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
c_title AS title, c_message AS message, c_domain AS domain,
|
||||||
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, c_maxtags AS maxtags,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_created AS created, c_revised AS revised
|
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_refid=?`),
|
WHERE c_refid=?`),
|
||||||
id)
|
id)
|
||||||
|
@ -80,8 +80,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
c_title AS title, c_message AS message, c_domain AS domain,
|
||||||
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, c_maxtags AS maxtags,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_created AS created, c_revised AS revised
|
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain=? AND c_active=true`),
|
WHERE c_domain=? AND c_active=true`),
|
||||||
subdomain)
|
subdomain)
|
||||||
|
@ -95,8 +95,8 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_title AS title, c_message AS message, c_domain AS domain,
|
c_title AS title, c_message AS message, c_domain AS domain,
|
||||||
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, c_maxtags AS maxtags,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_created AS created, c_revised AS revised
|
c_maxtags AS maxtags, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain='' AND c_active=true`))
|
WHERE c_domain='' AND c_active=true`))
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func (s Store) UpdateOrganization(ctx domain.RequestContext, org org.Organizatio
|
||||||
|
|
||||||
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
|
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
|
||||||
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email,
|
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email,
|
||||||
c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_revised=:revised
|
c_anonaccess=:allowanonymousaccess, c_sub=:subscription, c_maxtags=:maxtags, c_revised=:revised
|
||||||
WHERE c_refid=:refid`,
|
WHERE c_refid=:refid`,
|
||||||
&org)
|
&org)
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.add"
|
method := "page.add"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.update"
|
method := "page.update"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -510,7 +510,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.delete"
|
method := "page.delete"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -608,7 +608,7 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.delete.pages"
|
method := "page.delete.pages"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -721,7 +721,7 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.sequence"
|
method := "page.sequence"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -791,7 +791,7 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.level"
|
method := "page.level"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -987,7 +987,7 @@ func (h *Handler) GetDocumentRevisions(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.document.revisions"
|
method := "page.document.revisions"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1018,7 +1018,7 @@ func (h *Handler) GetRevisions(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.revisions"
|
method := "page.revisions"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1053,7 +1053,7 @@ func (h *Handler) GetDiff(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "page.diff"
|
method := "page.diff"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ func (h *Handler) DeleteUserPin(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ func (h *Handler) UpdatePinSequence(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
318
domain/product.go
Normal file
318
domain/product.go
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
// 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 domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"encoding/xml"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Edition is either Community or Enterprise.
|
||||||
|
type Edition string
|
||||||
|
|
||||||
|
// Package controls feature-set within edition.
|
||||||
|
type Package string
|
||||||
|
|
||||||
|
// Plan tells us if instance if self-hosted or Documize SaaS/Cloud.
|
||||||
|
type Plan string
|
||||||
|
|
||||||
|
// Seats represents number of users.
|
||||||
|
type Seats int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CommunityEdition is AGPL licensed open core of product.
|
||||||
|
CommunityEdition Edition = "Community"
|
||||||
|
|
||||||
|
// EnterpriseEdition is proprietary closed-source product.
|
||||||
|
EnterpriseEdition Edition = "Enterprise"
|
||||||
|
|
||||||
|
// PackageEssentials provides core capabilities.
|
||||||
|
PackageEssentials Package = "Essentials"
|
||||||
|
|
||||||
|
// PackageAdvanced provides analytics, reporting,
|
||||||
|
// content lifecycle, content verisoning, and audit logs.
|
||||||
|
PackageAdvanced Package = "Advanced"
|
||||||
|
|
||||||
|
// PackagePremium provides actions, feedback capture,
|
||||||
|
// approvals workflow, secure external sharing.
|
||||||
|
PackagePremium Package = "Premium"
|
||||||
|
|
||||||
|
// PackageDataCenter provides multi-tenanting
|
||||||
|
// and a bunch of professional services.
|
||||||
|
PackageDataCenter Package = "Data Center"
|
||||||
|
|
||||||
|
// PlanCloud represents *.documize.com hosting.
|
||||||
|
PlanCloud Plan = "Cloud"
|
||||||
|
|
||||||
|
// PlanSelfHost represents privately hosted Documize instance.
|
||||||
|
PlanSelfHost Plan = "Self-host"
|
||||||
|
|
||||||
|
// Seats0 is 0 users.
|
||||||
|
Seats0 Seats = 0
|
||||||
|
|
||||||
|
// Seats1 is 10 users.
|
||||||
|
Seats1 Seats = 10
|
||||||
|
|
||||||
|
// Seats2 is 25 users.
|
||||||
|
Seats2 Seats = 25
|
||||||
|
|
||||||
|
//Seats3 is 50 users.
|
||||||
|
Seats3 Seats = 50
|
||||||
|
|
||||||
|
// Seats4 is 100 users.
|
||||||
|
Seats4 Seats = 100
|
||||||
|
|
||||||
|
//Seats5 is 250 users.
|
||||||
|
Seats5 Seats = 250
|
||||||
|
|
||||||
|
// Seats6 is unlimited.
|
||||||
|
Seats6 Seats = 9999
|
||||||
|
)
|
||||||
|
|
||||||
|
// Product provides product meta information and handles
|
||||||
|
// subscription validation for Enterprise edition.
|
||||||
|
type Product struct {
|
||||||
|
Edition Edition
|
||||||
|
Title string
|
||||||
|
Version string
|
||||||
|
Major string
|
||||||
|
Minor string
|
||||||
|
Patch string
|
||||||
|
Revision int
|
||||||
|
|
||||||
|
// UserCount is number of users within Documize instance by tenant.
|
||||||
|
UserCount map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns if subscription is valid using RequestContext.
|
||||||
|
func (p *Product) IsValid(ctx RequestContext) bool {
|
||||||
|
// Community edition is always valid.
|
||||||
|
if p.Edition == CommunityEdition {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty means we cannot be valid.
|
||||||
|
if ctx.Subscription.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enterprise edition is valid if system has loaded up user count by tenant.
|
||||||
|
if uc, ok := p.UserCount[ctx.OrgID]; ok {
|
||||||
|
// Enterprise edition is valid if subcription date is greater than now and we have enough users/seats.
|
||||||
|
if time.Now().UTC().Before(ctx.Subscription.End) && uc <= int(ctx.Subscription.Seats) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// First 10 is free for Enterprise edition.
|
||||||
|
if Seats1 == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriptionData holds encrypted data and is unpacked into Subscription.
|
||||||
|
type SubscriptionData struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriptionXML represents subscription data as XML document.
|
||||||
|
type SubscriptionXML struct {
|
||||||
|
XMLName xml.Name `xml:"Documize"`
|
||||||
|
Key string
|
||||||
|
Signature string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscription data for customer.
|
||||||
|
type Subscription struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Edition Edition `json:"edition"`
|
||||||
|
Plan Plan `json:"plan"`
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Seats Seats `json:"seats"`
|
||||||
|
Trial bool `json:"trial"`
|
||||||
|
Price uint64 `json:"price"`
|
||||||
|
// Derived fields
|
||||||
|
ActiveUsers int `json:"activeUsers"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty determines if we have a license.
|
||||||
|
func (s *Subscription) IsEmpty() bool {
|
||||||
|
return s.Seats == Seats0 &&
|
||||||
|
len(s.Name) == 0 && len(s.Email) == 0 && s.Start.Year() == 1 && s.End.Year() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriptionUserAccount states number of active users by tenant.
|
||||||
|
type SubscriptionUserAccount struct {
|
||||||
|
OrgID string `json:"orgId"`
|
||||||
|
Users int `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriptionAsXML returns subscription data as XML document:
|
||||||
|
//
|
||||||
|
// <DocumizeLicense>
|
||||||
|
// <Key>some key</Key>
|
||||||
|
// <Signature>some signature</Signature>
|
||||||
|
// </DocumizeLicense>
|
||||||
|
//
|
||||||
|
// XML document is empty in case of error.
|
||||||
|
func SubscriptionAsXML(j SubscriptionData) (b []byte, err error) {
|
||||||
|
x := &SubscriptionXML{Key: j.Key, Signature: j.Signature}
|
||||||
|
b, err = xml.Marshal(x)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeSubscription returns Documize issued product licensing information.
|
||||||
|
func DecodeSubscription(sd SubscriptionData) (sub Subscription, err error) {
|
||||||
|
// Empty check.
|
||||||
|
if len(sd.Key) == 0 || len(sd.Signature) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ciphertext, signature []byte
|
||||||
|
ciphertext, _ = hex.DecodeString(sd.Key)
|
||||||
|
signature, _ = hex.DecodeString(sd.Signature)
|
||||||
|
|
||||||
|
// Load up keys.
|
||||||
|
serverBlock, _ := pem.Decode([]byte(serverPublicKeyPEM4096))
|
||||||
|
serverPublicKey, _ := x509.ParsePKIXPublicKey(serverBlock.Bytes)
|
||||||
|
clientBlock, _ := pem.Decode([]byte(clientPrivateKeyPEM4096))
|
||||||
|
clientPrivateKey, _ := x509.ParsePKCS1PrivateKey(clientBlock.Bytes)
|
||||||
|
|
||||||
|
label := []byte("dmzsub")
|
||||||
|
hash := sha256.New()
|
||||||
|
plainText, err := rsa.DecryptOAEP(hash, rand.Reader, clientPrivateKey, ciphertext, label)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check signature
|
||||||
|
var opts rsa.PSSOptions
|
||||||
|
opts.SaltLength = rsa.PSSSaltLengthAuto
|
||||||
|
PSSmessage := plainText
|
||||||
|
newhash := crypto.SHA256
|
||||||
|
pssh := newhash.New()
|
||||||
|
pssh.Write(PSSmessage)
|
||||||
|
hashed := pssh.Sum(nil)
|
||||||
|
|
||||||
|
err = rsa.VerifyPSS(serverPublicKey.(*rsa.PublicKey), newhash, hashed, signature, &opts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(plainText, &sub)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverPublicKeyPEM4096 = `
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICITANBgkqhkiG9w0BAQEFAAOCAg4AMIICCQKCAgB1/J5crBk0rK+zkPn6p4nf
|
||||||
|
qitsftN1/wrGq3xrXLhBax/+zyr3wm4Cd8bYANZjfzKw8jSoTqhoqwGF2J1A8Mjg
|
||||||
|
Orfn04UGsM/Em+5g2b6d/Uc3tyoR7DJYwr0coc0rPZaypneAhaf6ob266CU8QEdE
|
||||||
|
xkRkPMc/1TAOPmUkeuM2LI9Q/LDA5zPnN3WgYLGd7O1bSVOQYjw4KVp7Xr987Cec
|
||||||
|
CHWzrrjwQ7vRYUqxpz1kQ8ZAmhnFAkAQzScE87kPKM9V2Txo0NQ9aL2idP2FoVi0
|
||||||
|
obgGfJShI25YAeQncJBsyHV/uWxd3l/egaTHyQlcMgxBv61qsqgKzFZFsTNleQ3x
|
||||||
|
SR4i8QnNLk0hwtR+NREJZRlIdMGBwV7elJa+8v8Zw6lbC1J8OghNseggGcBOoG6v
|
||||||
|
OOwnEy6DK7hS3qfnHhFvR2zr9R5iQLHBIeVaVFZiLMKffRZnyHc3Dt5ozFMvpnzH
|
||||||
|
TBaHzydI57mrYZKv3s8hEgVJqMA9d1zCd9bwPwDIqiR/tYgPadwagQwHE4d4Pg8f
|
||||||
|
K8anfghelduKB0qIfeuQIEKmErEDK/qHj8HUC4nYUQy7hIo4F9D/HB22IfD6rM4D
|
||||||
|
BrswLjnIDcW9ox8Sv2wT5FsRJqdYE80gmG68QjrGPcwqwkO6WhgAfr/LXx2kJ2HI
|
||||||
|
BAMmkAYoyOaGK82XYKC14wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var clientPublicKeyPEM4096 = `
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmtXoHjZ4Ky7gMqp9gY4f
|
||||||
|
TQ+EhtgGxlkn3b48doQXhHemq/QyrcVj5FcHr9Um6pxop/HDQX2N7DEKX52ShFwa
|
||||||
|
Ccv7iWWcZ8secope3nNouO80v9umb0LqWqVvfZSP4QbwDZa231baFWtnn2yiiOmA
|
||||||
|
SkLmexLU+fmGht2Df9Q0gQLofGeE6YzLrdvnwa1NJHEiowgWaS5dsvsxoZV6zDXG
|
||||||
|
428drRQ/JVt7soQbZENn0jiGSM+Tm77eXjMSu1oK8tnr7vm8ylBXj4rw6P4ONp50
|
||||||
|
Dd+lERsdJFK5EaKN4xnWVVKayUlZTFE8ZAMXckF48dG8i9IgRkkEf7UcKekB/+hT
|
||||||
|
1zIKHwmFjUy81jAmU5jySHFHfaGkIQKoKGFXQQt6st9rPLSLOFi8jLHYbJAO/Zs5
|
||||||
|
DTaOoGLwDYcPMsgZswUyxySBUPDXDzg31sIJYl35GmZf6AX7vWvcX3C0NJxhnEFy
|
||||||
|
eXnyJMe3yUHOJmJmYT91V/IKmUl51xdCdb8Gy9wM2oee9QEvM8BJEctGrXmcCuVb
|
||||||
|
V7qkA79D3UK9QTbOthHsPWeWbaJDsmaxlwwp+crGTpcTLOyzwZdLaOr4bmNCQKUW
|
||||||
|
OC0hPqiwhHsxPwA8Je98EvjLT9YC23+dCN2OoN4cpnRtl/rYNtlCHnIQ1l+n4hvs
|
||||||
|
LMsDcJ/rlaak4OADM1YvNxUCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
var clientPrivateKeyPEM4096 = `
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJJwIBAAKCAgEAmtXoHjZ4Ky7gMqp9gY4fTQ+EhtgGxlkn3b48doQXhHemq/Qy
|
||||||
|
rcVj5FcHr9Um6pxop/HDQX2N7DEKX52ShFwaCcv7iWWcZ8secope3nNouO80v9um
|
||||||
|
b0LqWqVvfZSP4QbwDZa231baFWtnn2yiiOmASkLmexLU+fmGht2Df9Q0gQLofGeE
|
||||||
|
6YzLrdvnwa1NJHEiowgWaS5dsvsxoZV6zDXG428drRQ/JVt7soQbZENn0jiGSM+T
|
||||||
|
m77eXjMSu1oK8tnr7vm8ylBXj4rw6P4ONp50Dd+lERsdJFK5EaKN4xnWVVKayUlZ
|
||||||
|
TFE8ZAMXckF48dG8i9IgRkkEf7UcKekB/+hT1zIKHwmFjUy81jAmU5jySHFHfaGk
|
||||||
|
IQKoKGFXQQt6st9rPLSLOFi8jLHYbJAO/Zs5DTaOoGLwDYcPMsgZswUyxySBUPDX
|
||||||
|
Dzg31sIJYl35GmZf6AX7vWvcX3C0NJxhnEFyeXnyJMe3yUHOJmJmYT91V/IKmUl5
|
||||||
|
1xdCdb8Gy9wM2oee9QEvM8BJEctGrXmcCuVbV7qkA79D3UK9QTbOthHsPWeWbaJD
|
||||||
|
smaxlwwp+crGTpcTLOyzwZdLaOr4bmNCQKUWOC0hPqiwhHsxPwA8Je98EvjLT9YC
|
||||||
|
23+dCN2OoN4cpnRtl/rYNtlCHnIQ1l+n4hvsLMsDcJ/rlaak4OADM1YvNxUCAwEA
|
||||||
|
AQKCAgBNNDenSPWmYps76DLodJs662/jZLgMEsyEDqVLWxX24UpkF0Fl0DS82IBm
|
||||||
|
tlvPQ+oTQ8NeVmJ70QAhKQqzoNEC7Ykgu1+/iVJHPqOLO/SNsgiVWcqlU7JTPIZZ
|
||||||
|
EcikJbdwryPEPSRE5ecnYR2yMuvbG3ydBYjYlAj2GmHFTWRYp8CQt3VYlvHAYRQw
|
||||||
|
SF9cumTQ8elqzMm/wuy+azBtvqrLIM6lTKEn2XPWUXTvC4UrFzAuAgLR99wdEE5Y
|
||||||
|
yM8IxIyV/kSahHEEi/0P0A36QgwQFuHRo7lmMTFCj9E72dg7dxLjJwW1vhPksn3w
|
||||||
|
ZKEPwsrG1SFuql3p576BT0PF/GxA6KdiAR++DjP8w9Fj9TUlduNH7md7FLuu8zRe
|
||||||
|
lHqT9SyFsDGmpJWPuw5Xl9+PvLfZBXDiqDOhczKWmd+DglLKJQiQphUKVJCpJ0In
|
||||||
|
jHtLgPFFciPFJjTrlW6ROaK/1mFkaIXvpzj50reKrq2u/zD1SNSFGa5JpbWkN288
|
||||||
|
HrpGTB++dLMkYmhAzZc2HO58qz9Kr4VdCZP9EMLFruQLrnZprz/wpplgj+n382Nd
|
||||||
|
rpPbX4TSTOBgll4oaU5YwUuYegGa0G/uY9j3DG+bnaXy993wTC7VyupaI2jqxo3t
|
||||||
|
BfpJJk3i8Of+sidVzAR+FVkPCyYmW28XkEXEjL4DNsKV47snQQKCAQEA8rsRITYv
|
||||||
|
m3JePEKM2u/sGvxIGsXxge1G7y7bbOzfn1r22yThlasYocbF9kdxnyUffzmY/yQo
|
||||||
|
6FLK6L+B2o5c/U5OKSvy8Lk6tYpZPiZ1cCeScwxc0y2jiKodXrxStPTdTNB0JToX
|
||||||
|
RGVUhUMvlI40e7TQ4egucy8opd/LjdyC9OCe1fyK4p90b0TAwI20fOILs9nXhACr
|
||||||
|
rd3FZeiidm4xtYo1Z09kKjkozbgaOSWIzMXdY+jbAwfEqIWD0VAp2p2ryV4qAiaL
|
||||||
|
zk9XEYLXkmuK/5vgv16cJc67CjVSVBT+wG0IzzUMCbBeuoFsEiMPh0aM6+v0YRkc
|
||||||
|
9MkRjXvYoCI8KQKCAQEAo0zG/W65Yd6KIQ85vavqNfr/fYDG2rigfraWBuTflTsy
|
||||||
|
TjnNxkdNS5NYfm4BzlydWD5bQJaP0XP9W4lHgq23wh6FAfC0Yzwh3sBBXhi+R4v3
|
||||||
|
mgnwsgxuNLOxLXn4JP5hI8pu7fmC9PQlBywEhWjdubmOspeiL4rJQk0H76EQyCvR
|
||||||
|
/V8+C3SJnnCbI2fqMOpPn7GV06BFvYxohACNE+KCCe7Dt/QjzAxSgDl9yPyed+b7
|
||||||
|
8p/1dTxVkDAPcJXucubQR2moHqu6nnJxdOiGVMjlRouP6ji5pESMmIqOAtn9Vwke
|
||||||
|
svhzkm6zLAi7ZtxbWGTfVIsrl2IUBg3ino01h0YBDQKCAQAMCX7V+Mvvl4JY1qwJ
|
||||||
|
h3Bb/jrNKRfK66ti3R4AjtagHnCzeWa+d1enXiYfCnf1/m9Lbd3KeU6WBtUNKcIU
|
||||||
|
xo6R+TojDIzlpynkKtI2JM4aG7xFfE12I4NCmb0PH6OyWZpH3uaDmhfhSm0glq5b
|
||||||
|
XZn4sITTTyJOj/4iC7Eafd74qdL2pal1h5bMlcpBQkW7E7Kk3p6zax0YaDEL1reH
|
||||||
|
y/snF42CbAt5lJATc5fJUbUxAnbyJ3AE/HOiL8zTqngI4VzNhZ/rr2Grf3+/3I84
|
||||||
|
MaEY/+/rTZPMxC2+WdqVVN01SbLwI59PM7He6eAkHhz9BmCiqnbaAdbPxNDcBVI+
|
||||||
|
zrPRAoIBAAm5AogIVaVMGLFLNMbkO3enUBrq1ewj3fptaJVUfzNlaONbcbMCf8mm
|
||||||
|
Jjiw2A6vWPbuD4TS8hEodMdEbyuKqEw4gPbSnArkg6e9jqbJllqwLLfRK7GOJ+mf
|
||||||
|
YUcx4eJh+uqknOIyXueyuZmpt0MyMTFjqOldOdzWyJDYAUb1MgiZA1GwoAMSlzcF
|
||||||
|
wVbkUv9ClCcP7bnB6yUT/Q0O81dhvxhUTPbg5Fi7yxWzVpfm4pCFAi858uVeCEIj
|
||||||
|
emfbpWzV7USzN71LwDq62aJ6TbUymOQQXys04Wi0ZCKY7UeiLwFFm7xQKqFnUeen
|
||||||
|
RXEkYZPrvZhNCPVkc4jAvuNtyOga9OkCggEAHt/Jr+pbw2CSGo+ttXyF70W1st7Y
|
||||||
|
xrzqHzEdNiYH8EILTBI2Xaen2HUka8dnjBdMwnaB7KXfu1J3Fg3Ot7Wp8JhOQKWF
|
||||||
|
tY/F9sKbAeF2s8AdsMlq3zBkwtobwhI6vx/NWmQ0AP01uP3h1uFWRmPXc3NweOjk
|
||||||
|
T7ntGmUrRQUKCGE9lUL1QwOnp5y3ZwPD9goa/h+Hh6Z8Ax4UqIC2wj0wgLgExbCk
|
||||||
|
BNCyKXHWawjvYMCmqOOAlLzgVfgljFVgV3DfJKgGZ4d3jQEb3XMfoWpyz5d2yjZu
|
||||||
|
SO3B+gGCaaT1MkalPcH+j8EldrU2xTvmeaQUSndlCIR1hOugae0cNaaKBA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
|
@ -14,13 +14,11 @@ package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/core/event"
|
|
||||||
"github.com/documize/community/core/request"
|
"github.com/documize/community/core/request"
|
||||||
"github.com/documize/community/core/response"
|
"github.com/documize/community/core/response"
|
||||||
"github.com/documize/community/core/streamutil"
|
"github.com/documize/community/core/streamutil"
|
||||||
|
@ -128,96 +126,6 @@ func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) {
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// License returns product license
|
|
||||||
func (h *Handler) License(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := domain.GetRequestContext(r)
|
|
||||||
|
|
||||||
if !ctx.GlobalAdmin {
|
|
||||||
response.WriteForbiddenError(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config, _ := h.Store.Setting.Get("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.GlobalAdmin {
|
|
||||||
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: ""}
|
|
||||||
|
|
||||||
err1 := xml.Unmarshal([]byte(config), &x)
|
|
||||||
if err1 == nil {
|
|
||||||
lj.Key = x.Key
|
|
||||||
lj.Signature = x.Signature
|
|
||||||
} else {
|
|
||||||
h.Runtime.Log.Error("failed to XML unmarshal EDITION-LICENSE", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
j, err2 := json.Marshal(lj)
|
|
||||||
js := "{}"
|
|
||||||
if err2 == nil {
|
|
||||||
js = string(j)
|
|
||||||
} else {
|
|
||||||
h.Runtime.Log.Error("failed to JSON marshal EDITION-LICENSE", err2)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Store.Setting.Set("EDITION-LICENSE", js)
|
|
||||||
|
|
||||||
/* ctx.Transaction, err = h.Runtime.Db.Beginx()*/
|
|
||||||
//if err != nil {
|
|
||||||
//response.WriteServerError(w, method, err)
|
|
||||||
//return
|
|
||||||
//}
|
|
||||||
|
|
||||||
/*ctx.Transaction.Commit()*/
|
|
||||||
|
|
||||||
h.Runtime.Log.Info("License changed")
|
|
||||||
|
|
||||||
event.Handler().Publish(string(event.TypeSystemLicenseChange))
|
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeSystemLicense)
|
|
||||||
|
|
||||||
response.WriteEmpty(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthConfig returns installation-wide auth configuration
|
// AuthConfig returns installation-wide auth configuration
|
||||||
func (h *Handler) AuthConfig(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) AuthConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "global.auth"
|
method := "global.auth"
|
||||||
|
|
|
@ -12,27 +12,7 @@
|
||||||
// Package setting manages both global and user level settings
|
// Package setting manages both global and user level settings
|
||||||
package setting
|
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 {
|
type authData struct {
|
||||||
AuthProvider string `json:"authProvider"`
|
AuthProvider string `json:"authProvider"`
|
||||||
AuthConfig string `json:"authConfig"`
|
AuthConfig string `json:"authConfig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
<DocumizeLicense>
|
|
||||||
<Key>some key</Key>
|
|
||||||
<Signature>some signature</Signature>
|
|
||||||
</DocumizeLicense>
|
|
||||||
*/
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "space.add"
|
method := "space.add"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -582,7 +582,7 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "space.remove"
|
method := "space.remove"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "space.delete"
|
method := "space.delete"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ type UserStorer interface {
|
||||||
UpdateUserPassword(ctx domain.RequestContext, userID, salt, password string) (err error)
|
UpdateUserPassword(ctx domain.RequestContext, userID, salt, password string) (err error)
|
||||||
DeactiveUser(ctx domain.RequestContext, userID string) (err error)
|
DeactiveUser(ctx domain.RequestContext, userID string) (err error)
|
||||||
ForgotUserPassword(ctx domain.RequestContext, email, token string) (err error)
|
ForgotUserPassword(ctx domain.RequestContext, email, token string) (err error)
|
||||||
CountActiveUsers() (c int)
|
CountActiveUsers() (c []domain.SubscriptionUserAccount)
|
||||||
MatchUsers(ctx domain.RequestContext, text string, maxMatches int) (u []user.User, err error)
|
MatchUsers(ctx domain.RequestContext, text string, maxMatches int) (u []user.User, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "template.saved"
|
method := "template.saved"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -343,7 +343,7 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
|
||||||
d.UserID = ctx.UserID
|
d.UserID = ctx.UserID
|
||||||
d.Name = docTitle
|
d.Name = docTitle
|
||||||
|
|
||||||
if h.Runtime.Product.Edition == env.CommunityEdition {
|
if h.Runtime.Product.Edition == domain.CommunityEdition {
|
||||||
d.Lifecycle = workflow.LifecycleLive
|
d.Lifecycle = workflow.LifecycleLive
|
||||||
} else {
|
} else {
|
||||||
d.Lifecycle = sp.Lifecycle
|
d.Lifecycle = sp.Lifecycle
|
||||||
|
|
|
@ -2,6 +2,7 @@ package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/documize/community/domain/store"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
|
@ -13,33 +14,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupTest prepares test environment
|
// SetupTest prepares test environment
|
||||||
func SetupTest() (rt *env.Runtime, s *domain.Store, ctx domain.RequestContext) {
|
func SetupTest() (rt *env.Runtime, s *store
|
||||||
|
.Store, ctx domain.RequestContext) {
|
||||||
rt, s = startRuntime()
|
rt, s = startRuntime()
|
||||||
ctx = setupContext()
|
ctx = setupContext()
|
||||||
return rt, s, ctx
|
return rt, s, ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func startRuntime() (rt *env.Runtime, s *domain.Store) {
|
func startRuntime() (rt *env.Runtime, s *store.Store) {
|
||||||
rt = new(env.Runtime)
|
rt = new(env.Runtime)
|
||||||
s = new(domain.Store)
|
s = new(store.Store)
|
||||||
|
|
||||||
rt.Log = logging.NewLogger(false)
|
rt.Log = logging.NewLogger(false)
|
||||||
web.Embed = embed.NewEmbedder()
|
web.Embed = embed.NewEmbedder()
|
||||||
|
|
||||||
rt.Product = env.ProdInfo{}
|
rt.Product = env.Product{}
|
||||||
rt.Product.Major = "0"
|
rt.Product.Major = "0"
|
||||||
rt.Product.Minor = "0"
|
rt.Product.Minor = "0"
|
||||||
rt.Product.Patch = "0"
|
rt.Product.Patch = "0"
|
||||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||||
rt.Product.Edition = "Test"
|
rt.Product.Edition = "Test"
|
||||||
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
||||||
rt.Product.License = env.License{}
|
|
||||||
rt.Product.License.Seats = 1
|
|
||||||
rt.Product.License.Valid = true
|
|
||||||
rt.Product.License.Trial = false
|
|
||||||
rt.Product.License.Edition = "Community"
|
|
||||||
|
|
||||||
// parse settings from command line and environment
|
// parse settings from command line and environment
|
||||||
rt.Flags = env.ParseFlags()
|
rt.Flags = env.ParseFlags()
|
||||||
boot.InitRuntime(rt, s)
|
boot.InitRuntime(rt, s)
|
||||||
|
|
||||||
|
@ -56,7 +53,7 @@ func setupContext() domain.RequestContext {
|
||||||
ctx.Administrator = true
|
ctx.Administrator = true
|
||||||
ctx.Guest = false
|
ctx.Guest = false
|
||||||
ctx.Editor = true
|
ctx.Editor = true
|
||||||
ctx.Global = true
|
ctx.GlobalAdmin = true
|
||||||
ctx.UserID = "test"
|
ctx.UserID = "test"
|
||||||
ctx.OrgID = "test"
|
ctx.OrgID = "test"
|
||||||
return ctx
|
return ctx
|
||||||
|
|
|
@ -51,10 +51,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "user.Add"
|
method := "user.Add"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
if !h.Runtime.Product.License.IsValid() {
|
if !h.Runtime.Product.IsValid(ctx) {
|
||||||
response.WriteBadLicense(w)
|
response.WriteBadLicense(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Administrator {
|
if !ctx.Administrator {
|
||||||
response.WriteForbiddenError(w)
|
response.WriteForbiddenError(w)
|
||||||
return
|
return
|
||||||
|
@ -101,7 +100,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
requestedPassword := secrets.GenerateRandomPassword()
|
requestedPassword := secrets.GenerateRandomPassword()
|
||||||
userModel.Salt = secrets.GenerateSalt()
|
userModel.Salt = secrets.GenerateSalt()
|
||||||
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
|
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
|
||||||
userModel.LastVersion = ctx.AppVersion
|
userModel.LastVersion = fmt.Sprintf("v%s", h.Runtime.Product.Version)
|
||||||
|
|
||||||
// only create account if not dupe
|
// only create account if not dupe
|
||||||
addUser := true
|
addUser := true
|
||||||
|
|
|
@ -312,17 +312,11 @@ func (s Store) ForgotUserPassword(ctx domain.RequestContext, email, token string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountActiveUsers returns the number of active users in the system.
|
// CountActiveUsers returns the number of active users in the system.
|
||||||
func (s Store) CountActiveUsers() (c int) {
|
func (s Store) CountActiveUsers() (c []domain.SubscriptionUserAccount) {
|
||||||
row := s.Runtime.Db.QueryRow("SELECT count(*) FROM dmz_user WHERE c_refid IN (SELECT c_userid FROM dmz_user_account WHERE c_active=true)")
|
err := s.Runtime.Db.Select(&c, "SELECT c_orgid AS orgid, COUNT(*) AS users FROM dmz_user_account WHERE c_active=true GROUP BY c_orgid ORDER BY c_orgid")
|
||||||
|
|
||||||
err := row.Scan(&c)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
s.Runtime.Log.Error("CountActiveUsers", err)
|
s.Runtime.Log.Error("CountActiveUsers", err)
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -64,7 +64,7 @@ func InitRuntime(r *env.Runtime, s *store.Store) bool {
|
||||||
// Open connection to database
|
// Open connection to database
|
||||||
db, err := sqlx.Open(r.StoreProvider.DriverName(), r.StoreProvider.MakeConnectionString()) //r.Flags.DBConn
|
db, err := sqlx.Open(r.StoreProvider.DriverName(), r.StoreProvider.MakeConnectionString()) //r.Flags.DBConn
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Log.Error("unable to setup database", err)
|
r.Log.Error("unable to open database", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database handle
|
// Database handle
|
||||||
|
@ -94,6 +94,3 @@ func InitRuntime(r *env.Runtime, s *store.Store) bool {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clever way to detect database type:
|
|
||||||
// https://github.com/golang-sql/sqlexp/blob/c2488a8be21d20d31abf0d05c2735efd2d09afe4/quoter.go#L46
|
|
||||||
|
|
|
@ -14,8 +14,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/section"
|
"github.com/documize/community/domain/section"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
"github.com/documize/community/edition/boot"
|
"github.com/documize/community/edition/boot"
|
||||||
|
@ -36,27 +38,27 @@ func main() {
|
||||||
web.Embed = embed.NewEmbedder()
|
web.Embed = embed.NewEmbedder()
|
||||||
|
|
||||||
// product details
|
// product details
|
||||||
rt.Product = env.ProdInfo{}
|
rt.Product = domain.Product{}
|
||||||
rt.Product.Major = "1"
|
rt.Product.Major = "1"
|
||||||
rt.Product.Minor = "72"
|
rt.Product.Minor = "73"
|
||||||
rt.Product.Patch = "1"
|
rt.Product.Patch = "0"
|
||||||
rt.Product.Revision = 181022154519
|
rt.Product.Revision = 181111110016
|
||||||
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
|
||||||
rt.Product.Edition = "Community"
|
rt.Product.Edition = domain.CommunityEdition
|
||||||
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)
|
||||||
rt.Product.License = env.License{}
|
|
||||||
rt.Product.License.Seats = 1
|
|
||||||
rt.Product.License.Valid = true
|
|
||||||
rt.Product.License.Trial = false
|
|
||||||
rt.Product.License.Edition = "Community"
|
|
||||||
|
|
||||||
// setup store
|
// Setup data store.
|
||||||
s := store.Store{}
|
s := store.Store{}
|
||||||
|
|
||||||
// parse settings from command line and environment
|
// Parse flags/envars.
|
||||||
rt.Flags = env.ParseFlags()
|
flagsOK := false
|
||||||
flagsOK := boot.InitRuntime(&rt, &s)
|
rt.Flags, flagsOK = env.ParseFlags()
|
||||||
if flagsOK {
|
if !flagsOK {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
bootOK := boot.InitRuntime(&rt, &s)
|
||||||
|
if bootOK {
|
||||||
// runtime.Log = runtime.Log.SetDB(runtime.Db)
|
// runtime.Log = runtime.Log.SetDB(runtime.Db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1392
embed/bindata.go
1392
embed/bindata.go
File diff suppressed because one or more lines are too long
39
gui/app/components/customize/change-log.js
Normal file
39
gui/app/components/customize/change-log.js
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
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import Notifier from '../../mixins/notifier';
|
||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend(Notifier, {
|
||||||
|
appMeta: service(),
|
||||||
|
global: service(),
|
||||||
|
changelog: '',
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
let cacheBuster = + new Date();
|
||||||
|
$.ajax({
|
||||||
|
url: `https://storage.googleapis.com/documize/news/summary.html?cb=${cacheBuster}`,
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'html',
|
||||||
|
success: function (response) {
|
||||||
|
self.set('changelog', response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
}
|
||||||
|
});
|
|
@ -9,29 +9,30 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import { empty } from '@ember/object/computed';
|
import { empty } from '@ember/object/computed';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Notifier from '../../mixins/notifier';
|
import Notifier from '../../mixins/notifier';
|
||||||
|
import Modals from '../../mixins/modal';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend(Notifier, {
|
export default Component.extend(Notifier, Modals, {
|
||||||
appMeta: service(),
|
appMeta: service(),
|
||||||
global: service(),
|
global: service(),
|
||||||
LicenseError: empty('license'),
|
licenseError: empty('license'),
|
||||||
changelog: '',
|
subscription: null,
|
||||||
|
planCloud: false,
|
||||||
|
planSelfhost: false,
|
||||||
|
|
||||||
init() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
this.get('global').getSubscription().then((subs) => {
|
||||||
let self = this;
|
this.set('subscription', subs);
|
||||||
let cacheBuster = + new Date();
|
if (subs.plan === 'Installed') {
|
||||||
$.ajax({
|
this.set('planCloud', false);
|
||||||
url: `https://storage.googleapis.com/documize/news/summary.html?cb=${cacheBuster}`,
|
this.set('planSelfhost', true);
|
||||||
type: 'GET',
|
} else {
|
||||||
dataType: 'html',
|
this.set('planCloud', true);
|
||||||
success: function (response) {
|
this.set('planSelfhost', false);
|
||||||
self.set('changelog', response);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -40,10 +41,17 @@ export default Component.extend(Notifier, {
|
||||||
saveLicense() {
|
saveLicense() {
|
||||||
this.showWait();
|
this.showWait();
|
||||||
|
|
||||||
this.get('global').saveLicense(this.get('license')).then(() => {
|
this.get('global').setLicense(this.get('license')).then(() => {
|
||||||
this.showDone();
|
this.showDone();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
this.get('global').deactivate().then(() => {
|
||||||
|
this.showDone();
|
||||||
|
this.modalOpen("#deactivation-modal", {"show": true});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,9 +13,10 @@ import $ from 'jquery';
|
||||||
import { notEmpty } from '@ember/object/computed';
|
import { notEmpty } from '@ember/object/computed';
|
||||||
import { inject as service } from '@ember/service'
|
import { inject as service } from '@ember/service'
|
||||||
import ModalMixin from '../../mixins/modal';
|
import ModalMixin from '../../mixins/modal';
|
||||||
|
import TooltipMixin from '../../mixins/tooltip';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend(ModalMixin, {
|
export default Component.extend(ModalMixin, TooltipMixin, {
|
||||||
classNames: ['layout-header', 'non-printable'],
|
classNames: ['layout-header', 'non-printable'],
|
||||||
tagName: 'header',
|
tagName: 'header',
|
||||||
folderService: service('folder'),
|
folderService: service('folder'),
|
||||||
|
@ -69,6 +70,8 @@ export default Component.extend(ModalMixin, {
|
||||||
this.eventBus.subscribe('pinChange', this, 'setupPins');
|
this.eventBus.subscribe('pinChange', this, 'setupPins');
|
||||||
this.setupPins();
|
this.setupPins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.renderTooltips();
|
||||||
},
|
},
|
||||||
|
|
||||||
setupPins() {
|
setupPins() {
|
||||||
|
@ -87,6 +90,7 @@ export default Component.extend(ModalMixin, {
|
||||||
willDestroyElement() {
|
willDestroyElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
||||||
|
this.removeTooltips();
|
||||||
this.eventBus.unsubscribe('pinChange');
|
this.eventBus.unsubscribe('pinChange');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -113,6 +117,14 @@ export default Component.extend(ModalMixin, {
|
||||||
this.get('session').seenNewVersion();
|
this.get('session').seenNewVersion();
|
||||||
this.set('hasWhatsNew', false);
|
this.set('hasWhatsNew', false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onBilling() {
|
||||||
|
if (!this.get('session.isAdmin')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get('router').transitionTo('customize.billing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -141,6 +141,58 @@ let constants = EmberObject.extend({
|
||||||
MySQL: 'MySQL',
|
MySQL: 'MySQL',
|
||||||
PostgreSQL: 'PostgreSQL',
|
PostgreSQL: 'PostgreSQL',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Product is where we try to balance the fine line between useful open core
|
||||||
|
// and the revenue-generating proprietary edition.
|
||||||
|
Product: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
|
||||||
|
// CommunityEdition is AGPL licensed open core of product.
|
||||||
|
CommunityEdition: 'Community',
|
||||||
|
|
||||||
|
// EnterpriseEdition is proprietary closed-source product.
|
||||||
|
EnterpriseEdition: 'Enterprise',
|
||||||
|
|
||||||
|
// PackageEssentials provides core capabilities.
|
||||||
|
PackageEssentials: "Essentials",
|
||||||
|
|
||||||
|
// PackageAdvanced provides analytics, reporting,
|
||||||
|
// content lifecycle, content verisoning, and audit logs.
|
||||||
|
PackageAdvanced: "Advanced",
|
||||||
|
|
||||||
|
// PackagePremium provides actions, feedback capture,
|
||||||
|
// approvals workflow, secure external sharing.
|
||||||
|
PackagePremium: "Premium",
|
||||||
|
|
||||||
|
// PackageDataCenter provides multi-tenanting
|
||||||
|
// and a bunch of professional services.
|
||||||
|
PackageDataCenter: "Data Center",
|
||||||
|
|
||||||
|
// PlanCloud represents *.documize.com hosting.
|
||||||
|
PlanCloud: "Cloud",
|
||||||
|
|
||||||
|
// PlanSelfHost represents privately hosted Documize instance.
|
||||||
|
PlanSelfHost: "Self-host",
|
||||||
|
|
||||||
|
// Seats0 is 0 users.
|
||||||
|
Seats0: 0,
|
||||||
|
|
||||||
|
// Seats1 is 10 users.
|
||||||
|
Seats1: 10,
|
||||||
|
|
||||||
|
// Seats2 is 25 users.
|
||||||
|
Seats2: 25,
|
||||||
|
|
||||||
|
//Seats3 is 50 users.
|
||||||
|
Seats3: 50,
|
||||||
|
|
||||||
|
// Seats4 is 100 users.
|
||||||
|
Seats4: 100,
|
||||||
|
|
||||||
|
//Seats5 is 250 users.
|
||||||
|
Seats5: 250,
|
||||||
|
|
||||||
|
// Seats6 is unlimited.
|
||||||
|
Seats6: 9999
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default { constants }
|
export default { constants }
|
||||||
|
|
32
gui/app/helpers/formatted-price.js
Normal file
32
gui/app/helpers/formatted-price.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
|
export function formattedPrice(params) {
|
||||||
|
let pence = params[0];
|
||||||
|
|
||||||
|
if(is.not.number(pence)) {
|
||||||
|
return '$0'
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = parseInt(pence);
|
||||||
|
|
||||||
|
if(p === 0) {
|
||||||
|
return '$0'
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = pence / 100;
|
||||||
|
|
||||||
|
return `$` + a;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default helper(formattedPrice);
|
|
@ -1 +0,0 @@
|
||||||
{{customize/license-key license=model}}
|
|
|
@ -10,8 +10,8 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Route from '@ember/routing/route';
|
|
||||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
export default Route.extend(AuthenticatedRouteMixin, {
|
export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
appMeta: service(),
|
appMeta: service(),
|
||||||
|
@ -19,16 +19,15 @@ export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
global: service(),
|
global: service(),
|
||||||
|
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
if (!this.get("session.isGlobalAdmin")) {
|
if (!this.get("session.isAdmin")) {
|
||||||
this.transitionTo('auth.login');
|
this.transitionTo('auth.login');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return this.get('global').getLicense();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
this.get('browser').setTitle('Product Licensing & Updates');
|
this.get('browser').setTitle('Product Changelog');
|
||||||
}
|
}
|
||||||
});
|
});
|
1
gui/app/pods/customize/product/template.hbs
Normal file
1
gui/app/pods/customize/product/template.hbs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{{customize/change-log}}
|
|
@ -10,27 +10,49 @@
|
||||||
<div id="sidebar" class="sidebar">
|
<div id="sidebar" class="sidebar">
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<p>Configure authentication, SMTP, licensing and manage user accounts</p>
|
<p>Configure authentication, SMTP, licensing and manage user accounts</p>
|
||||||
|
|
||||||
<ul class="tabnav-control tabnav-control-centered w-75">
|
<ul class="tabnav-control tabnav-control-centered w-75">
|
||||||
{{#link-to 'customize.general' activeClass='selected' class="tab tab-vertical" tagName="li" }}General{{/link-to}}
|
{{#link-to 'customize.general' activeClass='selected' class="tab tab-vertical" tagName="li" }}General{{/link-to}}
|
||||||
{{#link-to 'customize.folders' activeClass='selected' class="tab tab-vertical" tagName="li" }}Spaces{{/link-to}}
|
|
||||||
{{#link-to 'customize.groups' activeClass='selected' class="tab tab-vertical" tagName="li" }}Groups{{/link-to}}
|
|
||||||
{{#link-to 'customize.users' activeClass='selected' class="tab tab-vertical" tagName="li" }}Users{{/link-to}}
|
|
||||||
{{#link-to 'customize.integrations' activeClass='selected' class="tab tab-vertical" tagName="li" }}Integrations{{/link-to}}
|
{{#link-to 'customize.integrations' activeClass='selected' class="tab tab-vertical" tagName="li" }}Integrations{{/link-to}}
|
||||||
{{#if session.isGlobalAdmin}}
|
{{#if session.isGlobalAdmin}}
|
||||||
{{#link-to 'customize.smtp' activeClass='selected' class="tab tab-vertical" tagName="li" }}SMTP{{/link-to}}
|
{{#link-to 'customize.smtp' activeClass='selected' class="tab tab-vertical" tagName="li" }}Mail{{/link-to}}
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-4" />
|
||||||
|
<ul class="tabnav-control tabnav-control-centered w-75">
|
||||||
|
{{#link-to 'customize.groups' activeClass='selected' class="tab tab-vertical" tagName="li" }}Groups{{/link-to}}
|
||||||
|
{{#link-to 'customize.users' activeClass='selected' class="tab tab-vertical" tagName="li" }}Users{{/link-to}}
|
||||||
|
{{#if session.isGlobalAdmin}}
|
||||||
{{#link-to 'customize.auth' activeClass='selected' class="tab tab-vertical" tagName="li" }}Authentication{{/link-to}}
|
{{#link-to 'customize.auth' activeClass='selected' class="tab tab-vertical" tagName="li" }}Authentication{{/link-to}}
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-4" />
|
||||||
|
<ul class="tabnav-control tabnav-control-centered w-75">
|
||||||
|
{{#link-to 'customize.folders' activeClass='selected' class="tab tab-vertical" tagName="li" }}Spaces{{/link-to}}
|
||||||
|
{{#if session.isGlobalAdmin}}
|
||||||
{{#link-to 'customize.search' activeClass='selected' class="tab tab-vertical" tagName="li" }}Search{{/link-to}}
|
{{#link-to 'customize.search' activeClass='selected' class="tab tab-vertical" tagName="li" }}Search{{/link-to}}
|
||||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
{{#link-to 'customize.audit' activeClass='selected' class="tab tab-vertical" tagName="li" }}Audit{{/link-to}}
|
{{#link-to 'customize.audit' activeClass='selected' class="tab tab-vertical" tagName="li" }}Audit{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
{{#link-to 'customize.archive' activeClass='selected' class="tab tab-vertical" tagName="li" }}Archive{{/link-to}}
|
{{#link-to 'customize.archive' activeClass='selected' class="tab tab-vertical" tagName="li" }}Archive{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if session.isGlobalAdmin}}
|
</ul>
|
||||||
{{#link-to 'customize.license' activeClass='selected' class="tab tab-vertical" tagName="li" }}Product{{/link-to}}
|
|
||||||
|
<div class="mt-4" />
|
||||||
|
<ul class="tabnav-control tabnav-control-centered w-75">
|
||||||
|
{{#link-to 'customize.backup' activeClass='selected' class="tab tab-vertical" tagName="li" }}Backup // Restore{{/link-to}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-4" />
|
||||||
|
<ul class="tabnav-control tabnav-control-centered w-75">
|
||||||
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
|
{{#link-to 'customize.billing' activeClass='selected' class="tab tab-vertical" tagName="li" }}Billing{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#link-to 'customize.backup' activeClass='selected' class="tab tab-vertical" tagName="li" }}Backup & Restore{{/link-to}}
|
{{#link-to 'customize.product' activeClass='selected' class="tab tab-vertical" tagName="li" }}Changelog{{/link-to}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{{/layout/middle-zone-sidebar}}
|
{{/layout/middle-zone-sidebar}}
|
||||||
|
|
|
@ -14,6 +14,11 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
|
||||||
|
|
||||||
export default Route.extend(AuthenticatedRouteMixin, {
|
export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
this.transitionTo('folders');
|
// this.transitionTo('folders');
|
||||||
|
},
|
||||||
|
|
||||||
|
activate: function () {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.browser.setTitleWithoutSuffix('Aw, Snap!');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
<h1>Not found</h1>
|
<div class="not-found">
|
||||||
|
<h1>Oops! That page couldn't be found.</h1>
|
||||||
|
<p>Maybe the content you're looking for is no longer available?</p>
|
||||||
|
<p> </p>
|
||||||
|
<a href="/">Return to Documize home page</a>
|
||||||
|
</div>
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
|
|
@ -80,8 +80,8 @@ export default Router.map(function () {
|
||||||
this.route('smtp', {
|
this.route('smtp', {
|
||||||
path: 'smtp'
|
path: 'smtp'
|
||||||
});
|
});
|
||||||
this.route('license', {
|
this.route('product', {
|
||||||
path: 'license'
|
path: 'product'
|
||||||
});
|
});
|
||||||
this.route('auth', {
|
this.route('auth', {
|
||||||
path: 'auth'
|
path: 'auth'
|
||||||
|
@ -101,6 +101,9 @@ export default Router.map(function () {
|
||||||
this.route('backup', {
|
this.route('backup', {
|
||||||
path: 'backup'
|
path: 'backup'
|
||||||
});
|
});
|
||||||
|
this.route('billing', {
|
||||||
|
path: 'billing'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import AjaxService from 'ember-ajax/services/ajax';
|
||||||
export default AjaxService.extend({
|
export default AjaxService.extend({
|
||||||
session: service(),
|
session: service(),
|
||||||
localStorage: service(),
|
localStorage: service(),
|
||||||
|
appMeta: service(),
|
||||||
host: config.apiHost,
|
host: config.apiHost,
|
||||||
namespace: config.apiNamespace,
|
namespace: config.apiNamespace,
|
||||||
|
|
||||||
|
@ -34,20 +35,27 @@ export default AjaxService.extend({
|
||||||
|
|
||||||
handleResponse(status, headers /*, payload*/) {
|
handleResponse(status, headers /*, payload*/) {
|
||||||
try {
|
try {
|
||||||
|
// Handle user permission changes.
|
||||||
let user = this.get('session.session.content.authenticated.user');
|
let user = this.get('session.session.content.authenticated.user');
|
||||||
let userUpdate = headers['x-documize-status'];
|
let userUpdate = headers['x-documize-status'];
|
||||||
let appVersion = headers['x-documize-version'];
|
let appVersion = headers['x-documize-version'];
|
||||||
|
|
||||||
// when unauthorized on local API AJAX calls, redirect to app root
|
// Unauthorized local API AJAX calls redirect to app root.
|
||||||
if (status === 401 && is.not.undefined(appVersion) && is.not.include(window.location.href, '/auth')) {
|
if (status === 401 && is.not.undefined(appVersion) && is.not.include(window.location.href, '/auth')) {
|
||||||
this.get('localStorage').clearAll();
|
this.get('localStorage').clearAll();
|
||||||
window.location.href = 'auth/login';
|
window.location.href = 'auth/login';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.get('session.authenticated') && is.not.empty(userUpdate) && is.not.undefined(userUpdate)) {
|
// Handle billing/licensing issue.
|
||||||
let latest = JSON.parse(userUpdate);
|
if (status === 402 || headers['x-documize-subscription'] === 'false') {
|
||||||
|
this.set('appMeta.valid', false);
|
||||||
|
}
|
||||||
|
|
||||||
if (!latest.active || user.editor !== latest.editor || user.admin !== latest.admin || user.analytics !== latest.analytics || user.viewUsers !== latest.viewUsers) {
|
if (this.get('session.authenticated') && is.not.empty(userUpdate) && is.not.undefined(userUpdate)) {
|
||||||
|
let latest = JSON.parse(userUpdate);
|
||||||
|
// Permission change means re-validation.
|
||||||
|
if (!latest.active || user.editor !== latest.editor || user.admin !== latest.admin ||
|
||||||
|
user.analytics !== latest.analytics || user.viewUsers !== latest.viewUsers) {
|
||||||
this.get('localStorage').clearAll();
|
this.get('localStorage').clearAll();
|
||||||
window.location.href = 'auth/login';
|
window.location.href = 'auth/login';
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,15 +38,10 @@ export default Service.extend({
|
||||||
secureMode: false,
|
secureMode: false,
|
||||||
maxTags: 3,
|
maxTags: 3,
|
||||||
storageProvider: '',
|
storageProvider: '',
|
||||||
|
location: 'selfhost',
|
||||||
// for major.minor semver release detection
|
|
||||||
// for bugfix releases, only admin is made aware of new release and end users see no What's New messaging
|
// for bugfix releases, only admin is made aware of new release and end users see no What's New messaging
|
||||||
updateAvailable: false,
|
updateAvailable: false,
|
||||||
|
|
||||||
invalidLicense() {
|
|
||||||
return this.valid === false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getBaseUrl(endpoint) {
|
getBaseUrl(endpoint) {
|
||||||
return [this.get('endpoint'), endpoint].join('/');
|
return [this.get('endpoint'), endpoint].join('/');
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,10 +41,23 @@ export default Service.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Returns product subscription.
|
||||||
|
getSubscription() {
|
||||||
|
if(this.get('sessionService.isAdmin')) {
|
||||||
|
return this.get('ajax').request(`subscription`, {
|
||||||
|
method: 'GET',
|
||||||
|
dataType: 'JSON'
|
||||||
|
}).then((response) => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Returns product license.
|
// Returns product license.
|
||||||
getLicense() {
|
getLicense() {
|
||||||
if(this.get('sessionService.isGlobalAdmin')) {
|
if(this.get('sessionService.isAdmin')) {
|
||||||
return this.get('ajax').request(`global/license`, {
|
return this.get('ajax').request(`license`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
dataType: "text"
|
dataType: "text"
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
@ -53,10 +66,10 @@ export default Service.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Saves product license.
|
// Saves product subscription data.
|
||||||
saveLicense(license) {
|
setLicense(license) {
|
||||||
if(this.get('sessionService.isGlobalAdmin')) {
|
if(this.get('sessionService.isAdmin')) {
|
||||||
return this.get('ajax').request(`global/license`, {
|
return this.get('ajax').request(`license`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
data: license
|
data: license
|
||||||
|
@ -221,5 +234,15 @@ export default Service.extend({
|
||||||
|
|
||||||
xhr.send(data);
|
xhr.send(data);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
deactivate() {
|
||||||
|
if(this.get('sessionService.isAdmin')) {
|
||||||
|
return this.get('ajax').request(`deactivate`, {
|
||||||
|
method: 'POST',
|
||||||
|
}).then(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -25,3 +25,10 @@
|
||||||
color: $color-stroke;
|
color: $color-stroke;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.not-found {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: $color-gray;
|
||||||
|
margin: 5rem 0;
|
||||||
|
}
|
||||||
|
|
|
@ -222,4 +222,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .deactivation-zone {
|
||||||
|
@include border-radius(3px);
|
||||||
|
border: 1px solid $color-red;
|
||||||
|
margin: 30px 0;
|
||||||
|
padding: 20px 20px;
|
||||||
|
background-color: lighten($color-red, 60%);
|
||||||
|
color: $color-off-black;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
37
gui/app/templates/components/customize/change-log.hbs
Normal file
37
gui/app/templates/components/customize/change-log.hbs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="view-customize">
|
||||||
|
<h1 class="admin-heading">{{appMeta.edition}} Edition {{appMeta.version}}</h1>
|
||||||
|
<h2 class="sub-heading">Enterprise Edition unlocks
|
||||||
|
<a class="" href="https://documize.com/pricing">premium capabilities and product support</a>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="product-update">
|
||||||
|
<div class="update-summary">
|
||||||
|
{{#if appMeta.updateAvailable}}
|
||||||
|
<a href="https://documize.com/downloads" class="caption">New version available</a>
|
||||||
|
<p class="instructions">
|
||||||
|
To upgrade, replace existing binary and restart Documize. Migrate between Community and Enterprise editions seamlessly.
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<div class="caption">Release Summary</div>
|
||||||
|
{{/if}}
|
||||||
|
<p>
|
||||||
|
<span class="color-off-black">Community Edition {{appMeta.communityLatest}}</span>
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-community-windows-amd64.exe" class="font-weight-bold">Windows</a> ·
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-community-linux-amd64" class="font-weight-bold">Linux</a> ·
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-community-darwin-amd64" class="font-weight-bold">macOS</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="color-off-black">Enterprise Edition {{appMeta.enterpriseLatest}}</span>
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-windows-amd64.exe" class="font-weight-bold color-blue">Windows</a> ·
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-linux-amd64" class="font-weight-bold color-blue">Linux</a> ·
|
||||||
|
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-darwin-amd64" class="font-weight-bold color-blue">macOS</a>
|
||||||
|
</p>
|
||||||
|
<div class="my-5" />
|
||||||
|
{{{changelog}}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,69 +1,167 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="view-customize">
|
<div class="view-customize">
|
||||||
<h1 class="admin-heading">{{appMeta.edition}} Edition {{appMeta.version}}</h1>
|
<h1 class="admin-heading">Product Billing & Subscription</h1>
|
||||||
<h2 class="sub-heading">Enterprise Edition unlocks
|
<h2 class="sub-heading">Active subscription details</h2>
|
||||||
<a class="" href="https://documize.com/pricing">premium capabilities and product support</a>
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="view-customize">
|
<div class="view-customize">
|
||||||
<form class="mt-5 ">
|
<form class="my-5 ">
|
||||||
<div class="form-group">
|
<div class="form-group row">
|
||||||
<label for="product-license-xml">Optional Enterprise Edition License Key</label>
|
<label for="sub-name" class="col-sm-4 col-form-label"></label>
|
||||||
{{textarea id="product-license-xml" value=license rows="18" class=(if LicenseError 'form-control is-invalid' 'form-control')}}
|
<div class="col-sm-7">
|
||||||
{{#if appMeta.valid}}
|
{{#if (eq subscription.status 0)}}
|
||||||
{{#if (eq appMeta.edition "Enterprise")}}
|
<h3 class="text-danger">Enjoy the Documize free plan!</h3>
|
||||||
<p class="mt-2 color-green">Registered to {{appMeta.license.email}} @ {{appMeta.license.name}}</p>
|
<a class="btn btn-success" href="https://documize.com/checkout?ref=app&l={{appMeta.location}}&id={{subscription.id}}&o={{appMeta.orgId}}&u={{subscription.seats}}&a={{subscription.activeUsers}}&e={{subscription.email}}">upgrade now ⟶</a>
|
||||||
<p class="mt-2 color-green">{{appMeta.license.package}} package up to {{appMeta.license.seats}} users</p>
|
|
||||||
{{#if appMeta.license.trial}}
|
|
||||||
<p class="mt-2 color-red">Trial expiry {{formatted-date appMeta.license.end}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p class="mt-2 color-green">Subscribed to {{formatted-date appMeta.license.end}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<small class="form-text text-muted">License key is XML format and activates Enterprise edition</small>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{#if (eq subscription.status 1)}}
|
||||||
<p class="mt-2 color-red">License is not valid — check user count and expiry date</p>
|
<h3 class="text-success">Nice, you have an active product subscription!</h3>
|
||||||
<p class="mt-2 color-gray">Registered to {{appMeta.license.email}} @ {{appMeta.license.name}}</p>
|
<a class="btn btn-success" href="https://documize.com/checkout?ref=app&l={{appMeta.location}}&id={{subscription.id}}&o={{appMeta.orgId}}&u={{subscription.seats}}&a={{subscription.activeUsers}}&e={{subscription.email}}">
|
||||||
<p class="mt-2 color-gray">{{appMeta.license.package}} package up to {{appMeta.license.seats}} users</p>
|
change plan ⟶
|
||||||
{{#if appMeta.license.trial}}
|
</a>
|
||||||
<p class="mt-2 color-gray">Trial expiry {{formatted-date appMeta.license.end}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p class="mt-2 color-gray">Subscribed to {{formatted-date appMeta.license.end}}</p>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{#if (eq subscription.status 2)}}
|
||||||
|
<h3 class="text-danger">Hmm, your product subscription has expired</h3>
|
||||||
|
<a class="btn btn-success" href="https://documize.com/checkout?ref=app&l={{appMeta.location}}&id={{subscription.id}}&o={{appMeta.orgId}}&u={{subscription.seats}}&a={{subscription.activeUsers}}&e={{subscription.email}}">
|
||||||
|
renew ⟶
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (eq subscription.status 3)}}
|
||||||
|
<h3 class="text-danger">Hmm, {{subscription.activeUsers}} active user count exceeds permitted {{subscription.seats}} user limit</h3>
|
||||||
|
<a class="btn btn-success" href="https://documize.com/checkout?ref=app&l={{appMeta.location}}&id={{subscription.id}}&o={{appMeta.orgId}}&u={{subscription.seats}}&a={{subscription.activeUsers}}&e={{subscription.email}}">
|
||||||
|
upgrade ⟶
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (eq subscription.status 4)}}
|
||||||
|
<h3 class="text-danger">Hmm, your product subscription is not valid</h3>
|
||||||
|
<a class="btn btn-success" href="https://documize.com/pricing?ref=app">upgrade ⟶</a>
|
||||||
|
{{/if}}
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn btn-success mt-3" {{action 'saveLicense'}}>Save</div>
|
<div class="form-group row">
|
||||||
|
<label for="sub-id" class="col-sm-4 col-form-label">Customer ID</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-id" type="text" value=subscription.id class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">Quote this ID when contacting us</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-name" class="col-sm-4 col-form-label">Customer Name</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-name" type="text" value=subscription.name class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">The business or personal name of our customer</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-email" class="col-sm-4 col-form-label">Contact Email</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-email" type="email" value=subscription.email class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">Where we will send product update and billing notices</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-seats" class="col-sm-4 col-form-label">Maximum Users</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-seats" type="number" value=subscription.seats class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">Your user pack size — you have {{subscription.activeUsers}} active users at the moment</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if (eq appMeta.location 'selfhost')}}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-start" class="col-sm-4 col-form-label">Start Date</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-start" type="text" value=(formatted-date subscription.start) class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">When you (re)signed up</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-end" class="col-sm-4 col-form-label">Renewal Date</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-end" type="text" value=(formatted-date subscription.end) class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">The renewal date of your annual subscription</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-price" class="col-sm-4 col-form-label">Annual Billing</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-price" type="text" value=(formatted-price subscription.price) class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">What you pay each year</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-start" class="col-sm-4 col-form-label">Start of Billing</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-start" type="text" value=(formatted-date subscription.start) class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">When we first charged your credit card — charged every 30 days thereafter</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-price" class="col-sm-4 col-form-label">Monthly Amount</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-price" type="text" value=(formatted-price subscription.price) class='form-control' readonly=true}}
|
||||||
|
<small class="form-text text-muted">What you pay us each month</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if subscription.trial}}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-trial" class="col-sm-4 col-form-label">Trial?</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{input id="sub-trial" type="text" value="Yes" class='form-control' readonly=true}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (eq appMeta.location 'selfhost')}}
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="sub-license" class="col-sm-4 col-form-label">Activation Key</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
{{focus-textarea id="sub-license" value=license rows="10" class=(if licenseError 'form-control is-invalid' 'form-control')}}
|
||||||
|
<small class="form-text text-muted">The activation key you received after sign-up</small>
|
||||||
|
<div class="btn btn-secondary mt-3" {{action 'saveLicense'}}>Activate</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-update">
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
<div class="update-summary">
|
{{#if (eq appMeta.location 'cloud')}}
|
||||||
{{#if appMeta.updateAvailable}}
|
<div class="row">
|
||||||
<a href="https://documize.com/downloads" class="caption">New version available</a>
|
<div class="col-sm-4"></div>
|
||||||
<p class="instructions">
|
<div class="col-sm-7">
|
||||||
To upgrade, replace existing binary and restart Documize. Migrate between Community and Enterprise editions seamlessly.
|
<div class="view-customize">
|
||||||
</p>
|
<div class="deactivation-zone">
|
||||||
{{else}}
|
<p>Let us know if you would like to close your account or cancel your subscription.</p>
|
||||||
<div class="caption">Release Summary</div>
|
<p><span class="font-weight-bold">WARNING: </span>All data will be deleted so please download a complete backup of all your data.</p>
|
||||||
{{/if}}
|
<p>Requests can take up to 24 hours to process.</p>
|
||||||
<p>
|
{{#link-to 'customize.backup' class="btn btn-success"}}PERFORM BACKUP{{/link-to}}
|
||||||
<span class="color-off-black">Community Edition {{appMeta.communityLatest}}</span>
|
<div class="button-gap" />
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-community-windows-amd64.exe" class="font-weight-bold">Windows</a> ·
|
<button class="btn btn-danger" {{action 'onDeactivate'}}>REQUEST ACCOUNT CLOSURE</button>
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-community-linux-amd64" class="font-weight-bold">Linux</a> ·
|
</div>
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-community-darwin-amd64" class="font-weight-bold">macOS</a>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
</div>
|
||||||
<span class="color-off-black">Enterprise Edition {{appMeta.enterpriseLatest}}</span>
|
{{/if}}
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-windows-amd64.exe" class="font-weight-bold color-blue">Windows</a> ·
|
{{/if}}
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-linux-amd64" class="font-weight-bold color-blue">Linux</a> ·
|
|
||||||
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-darwin-amd64" class="font-weight-bold color-blue">macOS</a>
|
<div id="deactivation-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
</p>
|
<div class="modal-dialog" role="document">
|
||||||
<div class="my-5" />
|
<div class="modal-content">
|
||||||
{{{changelog}}}
|
<div class="modal-header">Deactivation Requested</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Your request has been sent and will be processed shortly.</p>
|
||||||
|
<p>If you haven't already, perform a backup to download all your data.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
<li class="item cursor-auto">
|
<li class="item cursor-auto">
|
||||||
<img class="logo" src="/assets/img/icon-white-64x64.png" />
|
<img class="logo" src="/assets/img/icon-white-64x64.png" />
|
||||||
</li>
|
</li>
|
||||||
{{#if (eq appMeta.edition 'Community')}}
|
{{#if (eq appMeta.edition constants.Product.CommunityEdition)}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
{{#link-to "folders" class=(if (eq selectItem 'spaces') 'link selected' 'link')}}SPACES{{/link-to}}
|
{{#link-to "folders" class=(if (eq selectItem 'spaces') 'link selected' 'link')}}SPACES{{/link-to}}
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
{{#if session.viewDashboard}}
|
{{#if session.viewDashboard}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
{{#link-to "dashboard" class=(if (eq selectItem 'dashboard') 'link selected' 'link')}}ACTIONS{{/link-to}}
|
{{#link-to "dashboard" class=(if (eq selectItem 'dashboard') 'link selected' 'link')}}ACTIONS{{/link-to}}
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
<i class="material-icons">menu</i>
|
<i class="material-icons">menu</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-menu" aria-labelledby="top-nav-hamburger">
|
<div class="dropdown-menu" aria-labelledby="top-nav-hamburger">
|
||||||
{{#if (eq appMeta.edition 'Community')}}
|
{{#if (eq appMeta.edition constants.Product.CommunityEdition)}}
|
||||||
{{#link-to "folders" class="dropdown-item"}}Spaces{{/link-to}}
|
{{#link-to "folders" class="dropdown-item"}}Spaces{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
{{#link-to "folders" class="dropdown-item"}}Spaces{{/link-to}}
|
{{#link-to "folders" class="dropdown-item"}}Spaces{{/link-to}}
|
||||||
{{#if session.viewDashboard}}
|
{{#if session.viewDashboard}}
|
||||||
{{#link-to "dashboard" class="dropdown-item"}}Actions{{/link-to}}
|
{{#link-to "dashboard" class="dropdown-item"}}Actions{{/link-to}}
|
||||||
|
@ -55,12 +55,22 @@
|
||||||
<div class="col col-6 col-md-3">
|
<div class="col col-6 col-md-3">
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="buttons d-flex flex-wrap align-items-center">
|
<div class="buttons d-flex flex-wrap align-items-center">
|
||||||
|
{{#unless appMeta.valid}}
|
||||||
|
<div class="btn-group">
|
||||||
|
<div class="button-icon-gold animated infinite wobble slow delay-2s"
|
||||||
|
data-toggle="tooltip" data-placement="bottom" title="Please select product plan"
|
||||||
|
{{action 'onBilling'}}>
|
||||||
|
<i class="material-icons">report</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="button-icon-gap" />
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
{{#link-to "search" class="button-icon-white" }}
|
{{#link-to "search" class="button-icon-white" }}
|
||||||
<i class="material-icons">search</i>
|
<i class="material-icons">search</i>
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if session.authenticated}}
|
{{#if session.authenticated}}
|
||||||
{{#if hasPins}}
|
{{#if hasPins}}
|
||||||
<div class="button-icon-gap" />
|
<div class="button-icon-gap" />
|
||||||
|
@ -100,14 +110,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="profile-button">
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="profile-button">
|
||||||
{{#if session.isAdmin}}
|
{{#if session.isAdmin}}
|
||||||
{{#link-to 'customize.general' class="dropdown-item" }}Settings{{/link-to}}
|
{{#link-to 'customize.general' class="dropdown-item"}}Settings{{/link-to}}
|
||||||
|
{{#unless appMeta.valid}}
|
||||||
|
{{#link-to 'customize.billing' class="dropdown-item font-weight-bold color-red"}}Update Billing{{/link-to}}
|
||||||
|
{{/unless}}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#link-to 'profile' class="dropdown-item" }}Profile{{/link-to}}
|
{{#link-to 'profile' class="dropdown-item" }}Profile{{/link-to}}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
{{#if session.isGlobalAdmin}}
|
{{#if session.isGlobalAdmin}}
|
||||||
{{#if appMeta.updateAvailable}}
|
{{#if appMeta.updateAvailable}}
|
||||||
{{#link-to 'customize.license' class="dropdown-item font-weight-bold color-orange" }}Update available{{/link-to}}
|
{{#link-to 'customize.product' class="dropdown-item font-weight-bold color-orange" }}Update available{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<a href="#" class="dropdown-item {{if hasWhatsNew 'color-whats-new font-weight-bold'}}" {{action 'onShowWhatsNewModal'}}>What's New</a>
|
<a href="#" class="dropdown-item {{if hasWhatsNew 'color-whats-new font-weight-bold'}}" {{action 'onShowWhatsNewModal'}}>What's New</a>
|
||||||
|
@ -178,7 +191,7 @@
|
||||||
<div class="dotcom">
|
<div class="dotcom">
|
||||||
<a href="https://documize.com">https://documize.com</a>
|
<a href="https://documize.com">https://documize.com</a>
|
||||||
</div>
|
</div>
|
||||||
{{#if (eq appMeta.edition 'Community')}}
|
{{#if (eq appMeta.edition constants.Product.CommunityEdition)}}
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
© Documize Inc. All rights reserved.
|
© Documize Inc. All rights reserved.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "documize",
|
"name": "documize",
|
||||||
"version": "1.72.0",
|
"version": "1.73.0",
|
||||||
"description": "The Document IDE",
|
"description": "The Document IDE",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "",
|
"repository": "",
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/model"
|
"github.com/documize/community/model"
|
||||||
"github.com/documize/community/model/org"
|
"github.com/documize/community/model/org"
|
||||||
)
|
)
|
||||||
|
@ -30,7 +31,7 @@ type Manifest struct {
|
||||||
OrgID string `json:"org"`
|
OrgID string `json:"org"`
|
||||||
|
|
||||||
// Product edition at the time of the backup.
|
// Product edition at the time of the backup.
|
||||||
Edition string `json:"edition"`
|
Edition domain.Edition `json:"edition"`
|
||||||
|
|
||||||
// When the backup took place.
|
// When the backup took place.
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
|
6
model/doc.go
Normal file
6
model/doc.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Model contains data structures used throughout Documize.
|
||||||
|
//
|
||||||
|
// Models can be used by any other package hence this package
|
||||||
|
// should not import other packages to avoid cyclical dependencies.
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SitemapDocument details a document that can be exposed via Sitemap.
|
// SitemapDocument details a document that can be exposed via Sitemap.
|
||||||
|
@ -28,19 +29,18 @@ type SitemapDocument struct {
|
||||||
|
|
||||||
// SiteMeta holds information associated with an Organization.
|
// SiteMeta holds information associated with an Organization.
|
||||||
type SiteMeta struct {
|
type SiteMeta struct {
|
||||||
OrgID string `json:"orgId"`
|
OrgID string `json:"orgId"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
AllowAnonymousAccess bool `json:"allowAnonymousAccess"`
|
AllowAnonymousAccess bool `json:"allowAnonymousAccess"`
|
||||||
AuthProvider string `json:"authProvider"`
|
AuthProvider string `json:"authProvider"`
|
||||||
AuthConfig string `json:"authConfig"`
|
AuthConfig string `json:"authConfig"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Revision int `json:"revision"`
|
Revision int `json:"revision"`
|
||||||
MaxTags int `json:"maxTags"`
|
MaxTags int `json:"maxTags"`
|
||||||
Edition string `json:"edition"`
|
Edition domain.Edition `json:"edition"`
|
||||||
Valid bool `json:"valid"`
|
ConversionEndpoint string `json:"conversionEndpoint"`
|
||||||
ConversionEndpoint string `json:"conversionEndpoint"`
|
Storage env.StoreType `json:"storageProvider"`
|
||||||
License env.License `json:"license"`
|
Location string `json:"location"` // reserved for internal use
|
||||||
Storage env.StoreType `json:"storageProvider"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ package org
|
||||||
|
|
||||||
import "github.com/documize/community/model"
|
import "github.com/documize/community/model"
|
||||||
|
|
||||||
// Organization defines a company that uses this app.
|
// Organization defines a tenant that uses this app.
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
model.BaseEntity
|
model.BaseEntity
|
||||||
Company string `json:"company"`
|
Company string `json:"company"`
|
||||||
|
@ -28,4 +28,5 @@ type Organization struct {
|
||||||
MaxTags int `json:"maxTags"`
|
MaxTags int `json:"maxTags"`
|
||||||
Serial string `json:"serial"`
|
Serial string `json:"serial"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
|
Subscription string
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/core/response"
|
"github.com/documize/community/core/response"
|
||||||
|
@ -39,7 +40,7 @@ func (m *middleware) cors(w http.ResponseWriter, r *http.Request, next http.Hand
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS, PATCH")
|
w.Header().Set("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS, PATCH")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "host, content-type, accept, authorization, origin, referer, user-agent, cache-control, x-requested-with")
|
w.Header().Set("Access-Control-Allow-Headers", "host, content-type, accept, authorization, origin, referer, user-agent, cache-control, x-requested-with")
|
||||||
w.Header().Set("Access-Control-Expose-Headers", "x-documize-version, x-documize-status, x-documize-filename, Content-Disposition, Content-Length")
|
w.Header().Set("Access-Control-Expose-Headers", "x-documize-version, x-documize-status, x-documize-filename, x-documize-subscription, Content-Disposition, Content-Length")
|
||||||
|
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
w.Header().Add("X-Documize-Version", m.Runtime.Product.Version)
|
w.Header().Add("X-Documize-Version", m.Runtime.Product.Version)
|
||||||
|
@ -53,16 +54,6 @@ func (m *middleware) cors(w http.ResponseWriter, r *http.Request, next http.Hand
|
||||||
next(w, r)
|
next(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *middleware) metrics(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
||||||
w.Header().Add("X-Documize-Version", m.Runtime.Product.Version)
|
|
||||||
w.Header().Add("Cache-Control", "no-cache")
|
|
||||||
|
|
||||||
// Prevent page from being displayed in an iframe
|
|
||||||
w.Header().Add("X-Frame-Options", "DENY")
|
|
||||||
|
|
||||||
next(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorize secure API calls by inspecting authentication token.
|
// Authorize secure API calls by inspecting authentication token.
|
||||||
// request.Context provides caller user information.
|
// request.Context provides caller user information.
|
||||||
// Site meta sent back as HTTP custom headers.
|
// Site meta sent back as HTTP custom headers.
|
||||||
|
@ -97,7 +88,6 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
||||||
m.Runtime.Log.Info(fmt.Sprintf("unable to find org (domain: %s, orgID: %s)", dom, rc.OrgID))
|
m.Runtime.Log.Info(fmt.Sprintf("unable to find org (domain: %s, orgID: %s)", dom, rc.OrgID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteForbiddenError(w)
|
response.WriteForbiddenError(w)
|
||||||
m.Runtime.Log.Error(method, err)
|
m.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
|
@ -110,14 +100,6 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.Subdomain = org.Domain
|
rc.Subdomain = org.Domain
|
||||||
// dom := organization.GetSubdomainFromHost(r)
|
|
||||||
// dom2 := organization.GetRequestSubdomain(r)
|
|
||||||
|
|
||||||
// if org.Domain != dom && org.Domain != dom2 {
|
|
||||||
// m.Runtime.Log.Info(fmt.Sprintf("domain mismatch %s vs. %s vs. %s", dom, dom2, org.Domain))
|
|
||||||
// response.WriteUnauthorizedError(w)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// If we have bad auth token and the domain allows anon access
|
// If we have bad auth token and the domain allows anon access
|
||||||
// then we generate guest context.
|
// then we generate guest context.
|
||||||
|
@ -148,7 +130,6 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
||||||
rc.AppURL = r.Host
|
rc.AppURL = r.Host
|
||||||
rc.Subdomain = organization.GetSubdomainFromHost(r)
|
rc.Subdomain = organization.GetSubdomainFromHost(r)
|
||||||
rc.SSL = r.TLS != nil
|
rc.SSL = r.TLS != nil
|
||||||
rc.AppVersion = fmt.Sprintf("v%s", m.Runtime.Product.Version)
|
|
||||||
|
|
||||||
// get user IP from request
|
// get user IP from request
|
||||||
i := strings.LastIndex(r.RemoteAddr, ":")
|
i := strings.LastIndex(r.RemoteAddr, ":")
|
||||||
|
@ -163,6 +144,38 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
||||||
rc.ClientIP = fip
|
rc.ClientIP = fip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Product subscription checks for both product editions.
|
||||||
|
weeks := 52
|
||||||
|
if m.Runtime.Product.Edition == domain.CommunityEdition {
|
||||||
|
// Subscription for Community edition is always valid.
|
||||||
|
rc.Subscription = domain.Subscription{Edition: domain.CommunityEdition,
|
||||||
|
Seats: domain.Seats6,
|
||||||
|
Trial: false,
|
||||||
|
Start: time.Now().UTC(),
|
||||||
|
End: time.Now().UTC().Add(time.Hour * 24 * 7 * time.Duration(weeks))}
|
||||||
|
} else {
|
||||||
|
// Enterprise edition requires valid subscription data.
|
||||||
|
if len(strings.TrimSpace(org.Subscription)) > 0 {
|
||||||
|
sd := domain.SubscriptionData{}
|
||||||
|
es1 := json.Unmarshal([]byte(org.Subscription), &sd)
|
||||||
|
if es1 == nil {
|
||||||
|
rc.Subscription, err = domain.DecodeSubscription(sd)
|
||||||
|
if err != nil {
|
||||||
|
m.Runtime.Log.Error("unable to decode subscription for org "+rc.OrgID, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.Runtime.Log.Error("unable to load subscription for org "+rc.OrgID, es1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag all HTTP calls with subscription status
|
||||||
|
subs := "false"
|
||||||
|
if m.Runtime.Product.IsValid(rc) {
|
||||||
|
subs = "true"
|
||||||
|
}
|
||||||
|
w.Header().Add("X-Documize-Subscription", subs)
|
||||||
|
|
||||||
// Fetch user permissions for this org
|
// Fetch user permissions for this org
|
||||||
if rc.Authenticated {
|
if rc.Authenticated {
|
||||||
u, err := user.GetSecuredUser(rc, *m.Store, org.RefID, rc.UserID)
|
u, err := user.GetSecuredUser(rc, *m.Store, org.RefID, rc.UserID)
|
||||||
|
|
|
@ -210,8 +210,6 @@ func RegisterEndpoints(rt *env.Runtime, s *store.Store) {
|
||||||
// global admin routes
|
// global admin routes
|
||||||
AddPrivate(rt, "global/smtp", []string{"GET", "OPTIONS"}, nil, setting.SMTP)
|
AddPrivate(rt, "global/smtp", []string{"GET", "OPTIONS"}, nil, setting.SMTP)
|
||||||
AddPrivate(rt, "global/smtp", []string{"PUT", "OPTIONS"}, nil, setting.SetSMTP)
|
AddPrivate(rt, "global/smtp", []string{"PUT", "OPTIONS"}, nil, setting.SetSMTP)
|
||||||
AddPrivate(rt, "global/license", []string{"GET", "OPTIONS"}, nil, setting.License)
|
|
||||||
AddPrivate(rt, "global/license", []string{"PUT", "OPTIONS"}, nil, setting.SetLicense)
|
|
||||||
AddPrivate(rt, "global/auth", []string{"GET", "OPTIONS"}, nil, setting.AuthConfig)
|
AddPrivate(rt, "global/auth", []string{"GET", "OPTIONS"}, nil, setting.AuthConfig)
|
||||||
AddPrivate(rt, "global/auth", []string{"PUT", "OPTIONS"}, nil, setting.SetAuthConfig)
|
AddPrivate(rt, "global/auth", []string{"PUT", "OPTIONS"}, nil, setting.SetAuthConfig)
|
||||||
AddPrivate(rt, "global/search/status", []string{"GET", "OPTIONS"}, nil, meta.SearchStatus)
|
AddPrivate(rt, "global/search/status", []string{"GET", "OPTIONS"}, nil, meta.SearchStatus)
|
||||||
|
|
|
@ -80,7 +80,6 @@ func Start(rt *env.Runtime, s *store.Store, ready chan struct{}) {
|
||||||
n := negroni.New()
|
n := negroni.New()
|
||||||
n.Use(negroni.NewStatic(web.StaticAssetsFileSystem()))
|
n.Use(negroni.NewStatic(web.StaticAssetsFileSystem()))
|
||||||
n.Use(negroni.HandlerFunc(cm.cors))
|
n.Use(negroni.HandlerFunc(cm.cors))
|
||||||
n.Use(negroni.HandlerFunc(cm.metrics))
|
|
||||||
n.UseHandler(router)
|
n.UseHandler(router)
|
||||||
|
|
||||||
// tell caller we are ready to serve HTTP
|
// tell caller we are ready to serve HTTP
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue