diff --git a/app/app/components/section/github/type-editor.js b/app/app/components/section/github/type-editor.js index 9da06254..19c5c053 100644 --- a/app/app/components/section/github/type-editor.js +++ b/app/app/components/section/github/type-editor.js @@ -39,7 +39,6 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, config = { clientId: cfg.clientID, callbackUrl: cfg.authorizationCallbackURL, - token: "", owner: null, owner_name: "", repo: null, @@ -52,6 +51,8 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, branchLines: "30", state: null, issues: "", + userId: "", + pageId: page.get('id'), }; try { @@ -64,18 +65,19 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, config.branchLines = metaConfig.branchLines; config.state = metaConfig.state; config.issues = metaConfig.issues; - config.token = metaConfig.token; + config.userId = metaConfig.userId; + config.pageId = metaConfig.pageId; } catch (e) {} self.set('config', config); + self.set('config.pageId', page.get('id')); // On auth callback capture code let code = window.location.search; if (is.not.undefined(code) && is.not.null(code) && is.not.empty(code) && code !== "") { let tok = code.replace("?code=", ""); - self.set('config.token', tok); - self.get('sectionService').fetch(page, "set_token", self.get('config')) + self.get('sectionService').fetch(page, "saveSecret", { "token": tok }) .then(function () { console.log("github auth code saved to db"); self.send('authStage2'); @@ -84,14 +86,17 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, self.send('auth'); }); } else { - self.get('sectionService').fetch(page, "check_token", self.get('config')) - .then(function (cfg) { - self.set('config.token', cfg.token); + if (config.userId !== self.session.user.id) { + console.log("github auth wrong user ID, switching"); + self.set('config.userId', self.session.user.id); + } + self.get('sectionService').fetch(page, "checkAuth", self.get('config')) + .then(function () { console.log("github auth code valid"); self.send('authStage2'); }, function (error) { //jshint ignore: line console.log(error); - self.send('auth'); // only require auth if the db does not already know the token + self.send('auth'); // require auth if the db token is invalid }); } }, function (error) { //jshint ignore: line @@ -166,11 +171,11 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, getReportLists() { let reports = []; reports[0] = { - id: "commits_data", // used as method for fetching Go data + id: "commitsData", // used as method for fetching Go data name: "Commits on a branch" }; reports[1] = { - id: "issues_data", // used as method for fetching Go data + id: "issuesData", // used as method for fetching Go data name: "Issues" }; @@ -207,11 +212,11 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, this.set('showCommits', false); this.set('showLabels', false); switch (thisReport.id) { - case 'commits_data': + case 'commitsData': this.set('showCommits', true); this.getBranchLists(); break; - case "issues_data": + case 'issuesData': this.set('showLabels', true); this.getLabelLists(); break; @@ -356,6 +361,7 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, authStage2() { let self = this; + self.set('config.userId', this.session.user.id); self.set('authenticated', true); self.set('busy', true); let page = this.get('page'); @@ -375,7 +381,6 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, }, auth() { - let self = this; self.set('busy', true); self.set('authenticated', false); diff --git a/app/app/components/section/papertrail/type-editor.js b/app/app/components/section/papertrail/type-editor.js index f46452b8..31009ff9 100644 --- a/app/app/components/section/papertrail/type-editor.js +++ b/app/app/components/section/papertrail/type-editor.js @@ -16,40 +16,40 @@ import SectionMixin from '../../../mixins/section'; import netUtil from '../../../utils/net'; export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { - sectionService: Ember.inject.service('section'), - isDirty: false, - waiting: false, - authenticated: false, - config: {}, + sectionService: Ember.inject.service('section'), + isDirty: false, + waiting: false, + authenticated: false, + config: {}, items: {}, - didReceiveAttrs() { - let config = {}; + didReceiveAttrs() { + let config = {}; - try { - config = JSON.parse(this.get('meta.config')); - } catch (e) {} + try { + config = JSON.parse(this.get('meta.config')); + } catch (e) {} - if (is.empty(config)) { - config = { - APIToken: "", - query: "", + if (is.empty(config)) { + config = { + APIToken: "", + query: "", max: 10, group: null, system: null - }; - } + }; + } - this.set('config', config); + this.set('config', config); - if (this.get('config.APIToken').length > 0) { - this.send('auth'); - } - }, + if (this.get('config.APIToken').length > 0) { + this.send('auth'); + } + }, - willDestroyElement() { - this.destroyTooltips(); - }, + willDestroyElement() { + this.destroyTooltips(); + }, displayError(reason) { if (netUtil.isAjaxAccessError(reason)) { @@ -59,98 +59,102 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, } }, - actions: { - isDirty() { - return this.get('isDirty'); - }, + actions: { + isDirty() { + return this.get('isDirty'); + }, - auth() { + auth() { + console.log(this.get('config')); // missing data? - this.set('config.APIToken', this.get('config.APIToken').trim()); + this.set('config.APIToken', this.get('config.APIToken').trim()); - if (is.empty(this.get('config.APIToken'))) { - $("#papertrail-apitoken").addClass("error").focus(); - return; - } + if (is.empty(this.get('config.APIToken'))) { + $("#papertrail-apitoken").addClass("error").focus(); + return; + } - let page = this.get('page'); + let page = this.get('page'); let config = this.get('config'); - let self = this; + let self = this; - this.set('waiting', true); + this.set('waiting', true); - this.get('sectionService').fetch(page, "auth", config) - .then(function(response) { - self.set('authenticated', true); - self.set('items', response); + this.get('sectionService').fetch(page, "auth", config) + .then(function (response) { + self.set('authenticated', true); + self.set('items', response); + self.set('config.APIToken', '********'); // reset the api token once it has been sent to the host - self.get('sectionService').fetch(page, "options", config) - .then(function(response) { - self.set('options', response); - self.set('waiting', false); + self.get('sectionService').fetch(page, "options", config) + .then(function (response) { + self.set('options', response); + self.set('waiting', false); - let options = self.get('options'); - let group = _.findWhere(options.groups, {id: config.group.id}); - if (is.not.undefined(group)) { - Ember.set(config, 'group', group); - } - }, function(reason) { //jshint ignore: line - self.set('waiting', false); + let options = self.get('options'); + let group = _.findWhere(options.groups, { id: config.group.id }); + if (is.not.undefined(group)) { + Ember.set(config, 'group', group); + } + }, function (reason) { //jshint ignore: line + self.set('waiting', false); + self.displayError(reason); + }); + }, function (reason) { //jshint ignore: line + self.set('authenticated', false); + self.set('waiting', false); + self.set('config.APIToken', ''); // clear the api token self.displayError(reason); - }); - }, function(reason) { //jshint ignore: line - self.set('authenticated', false); - self.set('waiting', false); - self.displayError(reason); - }); - }, + $("#papertrail-apitoken").addClass("error").focus(); + }); + }, onGroupsChange(group) { let config = this.get('config'); let page = this.get('page'); let self = this; - this.set('isDirty', true); - this.set('config.group', group); - this.set('waiting', true); + this.set('isDirty', true); + this.set('config.group', group); + this.set('waiting', true); this.get('sectionService').fetch(page, "auth", config) - .then(function(response) { + .then(function (response) { + self.set('waiting', false); + self.set('items', response); + }, function (reason) { //jshint ignore: line self.set('waiting', false); - self.set('items', response); - }, function(reason) { //jshint ignore: line - self.set('waiting', false); self.displayError(reason); - }); - }, + }); + }, onSystemsChange(system) { let config = this.get('config'); let page = this.get('page'); let self = this; - this.set('isDirty', true); + this.set('isDirty', true); this.set('config.system', system); - this.set('waiting', true); + this.set('waiting', true); this.get('sectionService').fetch(page, "auth", config) - .then(function(response) { + .then(function (response) { + self.set('waiting', false); + self.set('items', response); + }, function (reason) { //jshint ignore: line self.set('waiting', false); - self.set('items', response); - }, function(reason) { //jshint ignore: line - self.set('waiting', false); self.displayError(reason); - }); - }, + }); + }, - onCancel() { - this.attrs.onCancel(); - }, + onCancel() { + this.attrs.onCancel(); + }, - onAction(title) { + onAction(title) { let self = this; - let page = this.get('page'); - let meta = this.get('meta'); - page.set('title', title); - meta.set('externalSource', true); + let page = this.get('page'); + let meta = this.get('meta'); + page.set('title', title); + meta.set('externalSource', true); let config = this.get('config'); let max = 10; @@ -161,25 +165,25 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, Ember.set(config, 'max', max); this.set('waiting', true); - this.get('sectionService').fetch(page, "auth", this.get('config')) - .then(function(response) { - self.set('items', response); - let items = self.get('items'); + this.get('sectionService').fetch(page, "auth", this.get('config')) + .then(function (response) { + self.set('items', response); + let items = self.get('items'); - if (items.events.length > max) { - items.events = items.events.slice(0, max); - } + if (items.events.length > max) { + items.events = items.events.slice(0, max); + } - meta.set('config', JSON.stringify(config)); - meta.set('rawBody', JSON.stringify(items)); + meta.set('config', JSON.stringify(config)); + meta.set('rawBody', JSON.stringify(items)); - self.set('waiting', false); - self.attrs.onAction(page, meta); - }, function(reason) { //jshint ignore: line - self.set('authenticated', false); - self.set('waiting', false); - self.showNotification(`Something went wrong, try again!`); - }); - } - } -}); + self.set('waiting', false); + self.attrs.onAction(page, meta); + }, function (reason) { //jshint ignore: line + self.set('authenticated', false); + self.set('waiting', false); + self.showNotification(`Something went wrong, try again!`); + }); + } + } +}); \ No newline at end of file diff --git a/app/app/services/audit.js b/app/app/services/audit.js index 95c15754..d3e143f7 100644 --- a/app/app/services/audit.js +++ b/app/app/services/audit.js @@ -14,61 +14,65 @@ import netUtil from '../utils/net'; import config from '../config/environment'; export default Ember.Service.extend({ - sessionService: Ember.inject.service('session'), - ready: false, - enabled: config.APP.auditEnabled, + sessionService: Ember.inject.service('session'), + ready: false, + enabled: config.APP.auditEnabled, appId: config.APP.intercomKey, - init() { - this.start(); - }, + init() { + this.start(); + }, - record(id) { - if (!this.get('enabled') || is.empty(this.get('appId'))) { - return; - } + record(id) { + if (!this.get('enabled') || this.get('appId').length === 0) { + return; + } - if (!this.get('ready')) { - this.start(); - } + if (!this.get('ready')) { + this.start(); + } - Intercom('trackEvent', id); //jshint ignore: line - Intercom('update'); //jshint ignore: line - }, + Intercom('trackEvent', id); //jshint ignore: line + Intercom('update'); //jshint ignore: line + }, - stop() { - Intercom('shutdown'); //jshint ignore: line - }, + stop() { + if (!this.get('enabled') || this.get('appId').length === 0) { + return; + } - start() { - let session = this.get('sessionService'); + Intercom('shutdown'); //jshint ignore: line + }, - if (this.get('appId') === "" || !this.get('enabled') || !session.authenticated || this.get('ready')) { - return; - } + start() { + let session = this.get('sessionService'); - this.set('ready', true); + if (this.get('appId') === "" || !this.get('enabled') || !session.authenticated || this.get('ready')) { + return; + } - window.intercomSettings = { - app_id: this.get('appId'), - name: session.user.firstname + " " + session.user.lastname, - email: session.user.email, - user_id: session.user.id, - "administrator": session.user.admin, - company: { - id: session.get('appMeta.orgId'), - name: session.get('appMeta.title').string, - "domain": netUtil.getSubdomain(), - "version": session.get('appMeta.version') - } - }; + this.set('ready', true); - if (!session.get('isMobile')) { - window.intercomSettings.widget = { - activator: "#IntercomDefaultWidget" - }; - } + window.intercomSettings = { + app_id: this.get('appId'), + name: session.user.firstname + " " + session.user.lastname, + email: session.user.email, + user_id: session.user.id, + "administrator": session.user.admin, + company: { + id: session.get('appMeta.orgId'), + name: session.get('appMeta.title').string, + "domain": netUtil.getSubdomain(), + "version": session.get('appMeta.version') + } + }; - window.Intercom('boot', window.intercomSettings); - }, -}); + if (!session.get('isMobile')) { + window.intercomSettings.widget = { + activator: "#IntercomDefaultWidget" + }; + } + + window.Intercom('boot', window.intercomSettings); + }, +}); \ No newline at end of file diff --git a/app/app/templates/components/section/papertrail/type-editor.hbs b/app/app/templates/components/section/papertrail/type-editor.hbs index 2d50af1d..a4759032 100644 --- a/app/app/templates/components/section/papertrail/type-editor.hbs +++ b/app/app/templates/components/section/papertrail/type-editor.hbs @@ -4,7 +4,11 @@
-
Papertrail Authentication
+
Papertrail Authentication + {{#if authenticated}} + Complete + {{/if}} +
Provide your Papertrail API token
@@ -12,7 +16,11 @@
API Token (from your profile)
{{focus-input id="papertrail-apitoken" type="password" value=config.APIToken readonly=isReadonly}}
-
Authenticate
+ {{#if authenticated}} +
Re-Authenticate
+ {{else}} +
Authenticate
+ {{/if}}
diff --git a/app/app/utils/net.js b/app/app/utils/net.js index 2ba35f64..3a9900ae 100644 --- a/app/app/utils/net.js +++ b/app/app/utils/net.js @@ -10,37 +10,37 @@ // https://documize.com function getSubdomain() { - if (is.ipv4(window.location.host)) { - return ""; - } + if (is.ipv4(window.location.host)) { + return ""; + } - let domain = ""; - let parts = window.location.host.split("."); + let domain = ""; + let parts = window.location.host.split("."); - if (parts.length > 1) { - domain = parts[0].toLowerCase(); - } + if (parts.length > 1) { + domain = parts[0].toLowerCase(); + } - return domain; + return domain; } function getAppUrl(domain) { - let parts = window.location.host.split("."); - parts.removeAt(0); + let parts = window.location.host.split("."); + parts.removeAt(0); - let leftOvers = parts.join("."); + let leftOvers = parts.join("."); - if (is.empty(domain)) { - domain = ""; - } else { - domain = domain + "."; - } + if (is.empty(domain)) { + domain = ""; + } else { + domain = domain + "."; + } - return window.location.protocol + "//" + domain + leftOvers; + return window.location.protocol + "//" + domain + leftOvers; } function isAjaxAccessError(reason) { - if (typeof reason === "undefined") { + if (typeof reason === "undefined" || typeof reason.errors === "undefined") { return false; } @@ -52,7 +52,7 @@ function isAjaxAccessError(reason) { } export default { - getSubdomain, - getAppUrl, + getSubdomain, + getAppUrl, isAjaxAccessError, -}; +}; \ No newline at end of file diff --git a/documize/api/endpoint/page_endpoint.go b/documize/api/endpoint/page_endpoint.go index 70f1ac4c..015bc7fa 100644 --- a/documize/api/endpoint/page_endpoint.go +++ b/documize/api/endpoint/page_endpoint.go @@ -79,6 +79,8 @@ func AddDocumentPage(w http.ResponseWriter, r *http.Request) { model.Meta.PageID = pageID model.Page.SetDefaults() model.Meta.SetDefaults() + model.Meta.OrgID = p.Context.OrgID + model.Meta.UserID = p.Context.UserID // page.Title = template.HTMLEscapeString(page.Title) tx, err := request.Db.Beginx() @@ -90,7 +92,8 @@ func AddDocumentPage(w http.ResponseWriter, r *http.Request) { p.Context.Transaction = tx - output, ok := provider.Render(model.Page.ContentType, model.Meta.Config, model.Meta.RawBody) + output, ok := provider.Render(model.Page.ContentType, + provider.NewContext(model.Meta.OrgID, model.Meta.UserID), model.Meta.Config, model.Meta.RawBody) if !ok { log.ErrorString("provider.Render could not find: " + model.Page.ContentType) } @@ -374,7 +377,7 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) { writeSuccessEmptyJSON(w) } -// UpdateDocumentPage will persiste changed page and note the fact +// UpdateDocumentPage will persist changed page and note the fact // that this is a new revision. If the page is the first in a document // then the corresponding document title will also be changed. func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) { @@ -432,7 +435,15 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) { model.Page.SetDefaults() model.Meta.SetDefaults() - output, ok := provider.Render(model.Page.ContentType, model.Meta.Config, model.Meta.RawBody) + oldPageMeta, err := p.GetPageMeta(pageID) + + if err != nil { + log.Error("unable to fetch old pagemeta record", err) + writeBadRequestError(w, method, err.Error()) + return + } + + output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, oldPageMeta.UserID), model.Meta.Config, model.Meta.RawBody) if !ok { log.ErrorString("provider.Render could not find: " + model.Page.ContentType) } @@ -452,7 +463,7 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) { return } - err = p.UpdatePageMeta(model.Meta) + err = p.UpdatePageMeta(model.Meta, true) // change the UserID to the current one log.IfErr(tx.Commit()) diff --git a/documize/api/endpoint/sections_endpoint.go b/documize/api/endpoint/sections_endpoint.go index bdd163f5..27d5c3e2 100644 --- a/documize/api/endpoint/sections_endpoint.go +++ b/documize/api/endpoint/sections_endpoint.go @@ -70,7 +70,7 @@ func RunSectionCommand(w http.ResponseWriter, r *http.Request) { return } - if !provider.Command(sectionName, w, r) { + if !provider.Command(sectionName, provider.NewContext(p.Context.OrgID, p.Context.UserID), w, r) { log.ErrorString("Unable to run provider.Command() for: " + sectionName) writeNotFoundError(w, "RunSectionCommand", sectionName) } @@ -125,14 +125,16 @@ func RefreshSections(w http.ResponseWriter, r *http.Request) { return } + pcontext := provider.NewContext(pm.OrgID, pm.UserID) + // Ask for data refresh - data, ok := provider.Refresh(page.ContentType, pm.Config, pm.RawBody) + data, ok := provider.Refresh(page.ContentType, pcontext, pm.Config, pm.RawBody) if !ok { log.ErrorString("provider.Refresh could not find: " + page.ContentType) } // Render again - body, ok := provider.Render(page.ContentType, pm.Config, data) + body, ok := provider.Render(page.ContentType, pcontext, pm.Config, data) if !ok { log.ErrorString("provider.Render could not find: " + page.ContentType) } @@ -153,7 +155,7 @@ func RefreshSections(w http.ResponseWriter, r *http.Request) { return } - err = p.UpdatePageMeta(pm) + err = p.UpdatePageMeta(pm, false) // do not change the UserID on this PageMeta if err != nil { writeGeneralSQLError(w, method, err) diff --git a/documize/api/entity/objects.go b/documize/api/entity/objects.go index cececabb..fd9dc9c7 100644 --- a/documize/api/entity/objects.go +++ b/documize/api/entity/objects.go @@ -204,6 +204,7 @@ type PageMeta struct { Created time.Time `json:"created"` Revised time.Time `json:"revised"` OrgID string `json:"orgId"` + UserID string `json:"userId"` DocumentID string `json:"documentId"` PageID string `json:"pageId"` RawBody string `json:"rawBody"` // a blob of data diff --git a/documize/api/request/config.go b/documize/api/request/config.go index b88c424e..59063f9d 100644 --- a/documize/api/request/config.go +++ b/documize/api/request/config.go @@ -67,9 +67,9 @@ func ConfigString(area, path string) (ret string) { return ret } -// UserConfigString fetches a configuration JSON element from the userconfig table. +// UserConfigGetJSON fetches a configuration JSON element from the userconfig table for a given orgid/userid combination. // Errors return the empty string. A blank path returns the whole JSON object, as JSON. -func (p *Persister) UserConfigString(area, path string) (ret string) { +func UserConfigGetJSON(orgid, userid, area, path string) (ret string) { if Db == nil { return "" } @@ -77,7 +77,7 @@ func (p *Persister) UserConfigString(area, path string) (ret string) { path = "." + path } sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `userconfig` WHERE `key` = '" + area + - "' AND `userid` = '" + p.Context.UserID + "';" + "' AND `orgid` = '" + orgid + "' AND `userid` = '" + userid + "';" stmt, err := Db.Preparex(sql) if err != nil { @@ -105,15 +105,16 @@ func (p *Persister) UserConfigString(area, path string) (ret string) { } -// UserConfigSetJSON writes a configuration JSON element to the userconfig table. -func (p *Persister) UserConfigSetJSON(area, json string) error { +// UserConfigSetJSON writes a configuration JSON element to the userconfig table for the current user. +func UserConfigSetJSON(orgid, userid, area, json string) error { if Db == nil { return errors.New("no database") } if area == "" { return errors.New("no area") } - sql := "INSERT INTO `userconfig` (`userid`,`key`,`config`) VALUES ('" + p.Context.UserID + "','" + area + "','" + json + + sql := "INSERT INTO `userconfig` (`orgid`,`userid`,`key`,`config`) " + + "VALUES ('" + orgid + "','" + userid + "','" + area + "','" + json + "') ON DUPLICATE KEY UPDATE `config`='" + json + "';" stmt, err := Db.Preparex(sql) diff --git a/documize/api/request/page.go b/documize/api/request/page.go index 3ac3e1d3..7e220d58 100644 --- a/documize/api/request/page.go +++ b/documize/api/request/page.go @@ -34,6 +34,7 @@ func (p *Persister) AddPage(model models.PageModel) (err error) { model.Page.SetDefaults() model.Meta.OrgID = p.Context.OrgID + model.Meta.UserID = p.Context.UserID model.Meta.DocumentID = model.Page.DocumentID model.Meta.Created = time.Now().UTC() model.Meta.Revised = time.Now().UTC() @@ -65,7 +66,7 @@ func (p *Persister) AddPage(model models.PageModel) (err error) { err = searches.Add(&databaseRequest{OrgID: p.Context.OrgID}, model.Page, model.Page.RefID) - stmt2, err := p.Context.Transaction.Preparex("INSERT INTO pagemeta (pageid, orgid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + stmt2, err := p.Context.Transaction.Preparex("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") defer utility.Close(stmt2) if err != nil { @@ -73,7 +74,7 @@ func (p *Persister) AddPage(model models.PageModel) (err error) { return } - _, err = stmt2.Exec(model.Meta.PageID, model.Meta.OrgID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised) + _, err = stmt2.Exec(model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised) if err != nil { log.Error("Unable to execute insert for page meta", err) @@ -291,12 +292,15 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis } // UpdatePageMeta persists meta information associated with a document page. -func (p *Persister) UpdatePageMeta(meta entity.PageMeta) (err error) { +func (p *Persister) UpdatePageMeta(meta entity.PageMeta,updateUserID bool) (err error) { err = nil meta.Revised = time.Now().UTC() + if updateUserID { + meta.UserID=p.Context.UserID + } var stmt *sqlx.NamedStmt - stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pagemeta SET documentid=:documentid, rawbody=:rawbody, config=:config, externalsource=:externalsource, revised=:revised WHERE orgid=:orgid AND pageid=:pageid") + stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pagemeta SET userid=:userid, documentid=:documentid, rawbody=:rawbody, config=:config, externalsource=:externalsource, revised=:revised WHERE orgid=:orgid AND pageid=:pageid") defer utility.Close(stmt) if err != nil { @@ -383,7 +387,7 @@ func (p *Persister) DeletePage(documentID, pageID string) (rows int64, err error func (p *Persister) GetPageMeta(pageID string) (meta entity.PageMeta, err error) { err = nil - stmt, err := Db.Preparex("SELECT id, pageid, orgid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?") + stmt, err := Db.Preparex("SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?") defer utility.Close(stmt) if err != nil { @@ -409,7 +413,7 @@ func (p *Persister) GetDocumentPageMeta(documentID string, externalSourceOnly bo filter = " AND externalsource=1" } - err = Db.Select(&meta, "SELECT id, pageid, orgid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND documentid=?"+filter, p.Context.OrgID, documentID) + err = Db.Select(&meta, "SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND documentid=?"+filter, p.Context.OrgID, documentID) if err != nil { log.Error(fmt.Sprintf("Unable to execute select document page meta for org %s and document %s", p.Context.OrgID, documentID), err) diff --git a/documize/database/scripts/autobuild/db_00000.sql b/documize/database/scripts/autobuild/db_00000.sql index 481c8cbe..710a3ecd 100644 --- a/documize/database/scripts/autobuild/db_00000.sql +++ b/documize/database/scripts/autobuild/db_00000.sql @@ -168,6 +168,7 @@ CREATE TABLE IF NOT EXISTS `pagemeta` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `pageid` CHAR(16) NOT NULL COLLATE utf8_bin, `orgid` CHAR(16) NOT NULL COLLATE utf8_bin, + `userid` CHAR(16) NOT NULL COLLATE utf8_bin, `documentid` CHAR(16) NOT NULL COLLATE utf8_bin, `rawbody` LONGBLOB, `config` JSON, @@ -270,8 +271,12 @@ INSERT INTO `config` VALUES ('SECTION-TRELLO','{\"appKey\": \"\"}'); DROP TABLE IF EXISTS `userconfig`; CREATE TABLE IF NOT EXISTS `userconfig` ( + `orgid` CHAR(16) NOT NULL COLLATE utf8_bin, `userid` CHAR(16) NOT NULL COLLATE utf8_bin, `key` CHAR(225) NOT NULL, `config` JSON, - UNIQUE INDEX `idx_userconfig_userkey` (`userid`, `key` ASC) ) -DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; \ No newline at end of file + UNIQUE INDEX `idx_userconfig_orguserkey` (`orgid`, `userid`, `key` ASC) ) + DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; + +-- TODO insert userid into pagemeta table +-- ALTER TABLE `pagemeta` ADD `userid` CHAR(16) NOT NULL COLLATE utf8_bin AFTER `orgid`; diff --git a/documize/section/asana/asana.go b/documize/section/asana/asana.go index 19a0b212..47c64711 100644 --- a/documize/section/asana/asana.go +++ b/documize/section/asana/asana.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/code/code.go b/documize/section/code/code.go index fa28c118..8d8370e3 100644 --- a/documize/section/code/code.go +++ b/documize/section/code/code.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/docusign/docusign.go b/documize/section/docusign/docusign.go index fa02d8d4..03ce4a22 100644 --- a/documize/section/docusign/docusign.go +++ b/documize/section/docusign/docusign.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/gemini/gemini.go b/documize/section/gemini/gemini.go index 4478c286..8dd2a64d 100644 --- a/documize/section/gemini/gemini.go +++ b/documize/section/gemini/gemini.go @@ -40,7 +40,7 @@ func (*Provider) Meta() provider.TypeMeta { } // Render converts Gemini data into HTML suitable for browser rendering. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { var items []geminiItem var payload = geminiRender{} var c = geminiConfig{} @@ -64,7 +64,7 @@ func (*Provider) Render(config, data string) string { } // Command handles authentication, workspace listing and items retrieval. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { query := r.URL.Query() method := query.Get("method") @@ -84,7 +84,7 @@ func (*Provider) Command(w http.ResponseWriter, r *http.Request) { } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) (newData string) { +func (*Provider) Refresh(ctx *provider.Context, config, data string) (newData string) { var c = geminiConfig{} err := json.Unmarshal([]byte(config), &c) diff --git a/documize/section/github/github.go b/documize/section/github/github.go index 8b046228..d872db84 100644 --- a/documize/section/github/github.go +++ b/documize/section/github/github.go @@ -65,9 +65,22 @@ func authorizationCallbackURL() string { // NOTE: URL value must have the path and query "/api/public/validate?section=github" return request.ConfigString(meta.ConfigHandle(), "authorizationCallbackURL") } +func validateToken(ptoken string) error { + // Github authorization check + authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{ + Username: clientID(), + Password: clientSecret(), + }).Client()) + _, _, err := authClient.Authorizations.Check(clientID(), ptoken) + return err +} + +func secretsJSON(token string) string { + return `{"token":"` + strings.TrimSpace(token) + `"}` +} // Command to run the various functions required... -func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { +func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { query := r.URL.Query() method := query.Get("method") @@ -100,6 +113,25 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { return } + // get the secret token in the database + ptoken := ctx.GetSecrets("token") + + switch method { + + case "saveSecret": // secret Token update code + + // write the new one, direct from JS + if err = ctx.SaveSecrets(string(body)); err != nil { + log.Error("github settoken configuration", err) + provider.WriteError(w, "github", err) + return + } + provider.WriteEmpty(w) + return + + } + + // load the config from the client-side config := githubConfig{} err = json.Unmarshal(body, &config) @@ -110,64 +142,30 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { } config.Clean() + // always use DB version of the token + config.Token = ptoken - persist := request.GetPersister(r) + client := p.githubClient(config) - ptoken := persist.UserConfigString(meta.ContentType, "token") + switch method { // the main data handling switch - switch method { // the token handling switch - case "set_token": - // write the new one - if err = persist.UserConfigSetJSON(meta.ContentType, `{"token":"`+config.Token+`"}`); err != nil { - log.Error("github settoken configuration", err) + case "checkAuth": + + if len(ptoken) == 0 { + err = errors.New("empty github token") + } else { + err = validateToken(ptoken) + } + if err != nil { + // token now invalid, so wipe it + ctx.SaveSecrets("") // ignore error, already in an error state + log.Error("github check token validation", err) provider.WriteError(w, "github", err) return } provider.WriteEmpty(w) return - case "check_token": - if config.Token != ptoken { - // user github token does not match that in the database, so use DB version as the section version may be out-of-date - config.Token = ptoken - } - if err = config.TokenCheck(); err != nil { - log.Error("github checktoken validation", err) - provider.WriteError(w, "github", err) - return - } - provider.WriteJSON(w, config) - return - - default: - if config.Token != ptoken { - if len(config.Token) == 0 { - if len(ptoken) == 0 { - err = errors.New("missing github token") - } else { - config.Token = ptoken // use database one - } - } else { - // this is important when switching user - // tokens are different... - if len(ptoken) == 0 { - err = errors.New("no user github token") - } else { - config.Token = ptoken // use database one - } - } - } - if err != nil { - log.Error("github clean token configuration", err) - provider.WriteError(w, "github", err) - return - } - } - - client := p.githubClient(config) - - switch method { // the main data handling switch - case tagCommitsData: render, err := p.getCommits(client, config) @@ -255,7 +253,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { provider.WriteError(w, "github", err) return } - for kr, vr := range repos { + for _, vr := range repos { private := "" if *vr.Private { private = " (private)" @@ -263,7 +261,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { render = append(render, githubRepo{ Name: config.Owner + "/" + *vr.Name + private, - ID: fmt.Sprintf("%s:%s:%d", config.Owner, *vr.Name, kr), + ID: fmt.Sprintf("%s:%s", config.Owner, *vr.Name), Owner: config.Owner, Repo: *vr.Name, Private: *vr.Private, @@ -276,6 +274,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, render) case "branches": + if config.Owner == "" || config.Repo == "" { provider.WriteJSON(w, []githubBranch{}) // we have nothing to return return @@ -291,7 +290,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { for kc, vb := range branches { render[kc] = githubBranch{ Name: *vb.Name, - ID: fmt.Sprintf("%s:%s:%s:%d", config.Owner, config.Repo, *vb.Name, kc), + ID: fmt.Sprintf("%s:%s:%s", config.Owner, config.Repo, *vb.Name), Included: false, URL: "https://github.com/" + config.Owner + "/" + config.Repo + "/tree/" + *vb.Name, } @@ -300,6 +299,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, render) case "labels": + if config.Owner == "" || config.Repo == "" { provider.WriteJSON(w, []githubBranch{}) // we have nothing to return return @@ -324,6 +324,7 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, render) default: + log.ErrorString("Github connector unknown method: " + method) provider.WriteEmpty(w) } @@ -620,7 +621,7 @@ func (*Provider) getCommits(client *gogithub.Client, config githubConfig) ([]git } // Refresh ... gets the latest version -func (p *Provider) Refresh(configJSON, data string) string { +func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) string { var c = githubConfig{} err := json.Unmarshal([]byte(configJSON), &c) @@ -631,6 +632,7 @@ func (p *Provider) Refresh(configJSON, data string) string { } c.Clean() + c.Token = ctx.GetSecrets("token") switch c.ReportInfo.ID { /*case "issuenum_data": @@ -681,7 +683,7 @@ func (p *Provider) Refresh(configJSON, data string) string { } // Render ... just returns the data given, suitably formatted -func (p *Provider) Render(config, data string) string { +func (p *Provider) Render(ctx *provider.Context, config, data string) string { var err error payload := githubRender{} @@ -695,6 +697,8 @@ func (p *Provider) Render(config, data string) string { } c.Clean() + c.Token = ctx.GetSecrets("token") + payload.Config = c payload.Repo = c.RepoInfo payload.Limit = c.BranchLines diff --git a/documize/section/github/model.go b/documize/section/github/model.go index 7e822262..6cf52894 100644 --- a/documize/section/github/model.go +++ b/documize/section/github/model.go @@ -13,15 +13,13 @@ package github import ( "html/template" - "strings" "time" "github.com/documize/community/wordsmith/log" - gogithub "github.com/google/go-github/github" ) -const tagIssuesData = "issues_data" -const tagCommitsData = "commits_data" +const tagIssuesData = "issuesData" +const tagCommitsData = "commitsData" type githubRender struct { Config githubConfig @@ -219,8 +217,9 @@ type githubIssueActivity struct { */ type githubConfig struct { - AppKey string `json:"appKey"` // TODO keep? - Token string `json:"token"` + Token string `json:"-"` // NOTE very important that the secret Token is not leaked to the client side, so "-" + UserID string `json:"userId"` + PageID string `json:"pageId"` Owner string `json:"owner_name"` Repo string `json:"repo_name"` Branch string `json:"branch"` @@ -240,8 +239,6 @@ type githubConfig struct { } func (c *githubConfig) Clean() { - c.AppKey = strings.TrimSpace(c.AppKey) // TODO keep? - c.Token = strings.TrimSpace(c.Token) c.Owner = c.OwnerInfo.Name c.Repo = c.RepoInfo.Repo for _, l := range c.Lists { @@ -264,18 +261,6 @@ func (c *githubConfig) Clean() { c.SincePtr = &since } } - -} - -func (c *githubConfig) TokenCheck() error { - // Github authorization check - authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{ - Username: clientID(), - Password: clientSecret(), - }).Client()) - - _, _, err := authClient.Authorizations.Check(clientID(), c.Token) - return err } type githubCallbackT struct { diff --git a/documize/section/intercom/intercom.go b/documize/section/intercom/intercom.go index b3ea5e8c..5abe1c66 100644 --- a/documize/section/intercom/intercom.go +++ b/documize/section/intercom/intercom.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/mailchimp/mailchimp.go b/documize/section/mailchimp/mailchimp.go index 9a09fd50..cff56cd0 100644 --- a/documize/section/mailchimp/mailchimp.go +++ b/documize/section/mailchimp/mailchimp.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/markdown/markdown.go b/documize/section/markdown/markdown.go index 924abf7e..5e739c3c 100644 --- a/documize/section/markdown/markdown.go +++ b/documize/section/markdown/markdown.go @@ -36,18 +36,18 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render converts markdown data into HTML suitable for browser rendering. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { result := blackfriday.MarkdownCommon([]byte(data)) return string(result) } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/papertrail/model.go b/documize/section/papertrail/model.go index 01dede45..914ce855 100644 --- a/documize/section/papertrail/model.go +++ b/documize/section/papertrail/model.go @@ -61,7 +61,7 @@ type papertrailEvent struct { } type papertrailConfig struct { - APIToken string `json:"APIToken"` + APIToken string `json:"APIToken"` // only contains the correct token just after it is typed in Query string `json:"query"` Max int `json:"max"` Group papertrailOption `json:"group"` diff --git a/documize/section/papertrail/papertrail.go b/documize/section/papertrail/papertrail.go index 98339bd2..6e12f874 100644 --- a/documize/section/papertrail/papertrail.go +++ b/documize/section/papertrail/papertrail.go @@ -42,7 +42,7 @@ func (*Provider) Meta() provider.TypeMeta { } // Render converts Papertrail data into HTML suitable for browser rendering. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { var search papertrailSearch var events []papertrailEvent var payload = papertrailRender{} @@ -51,6 +51,8 @@ func (*Provider) Render(config, data string) string { json.Unmarshal([]byte(data), &search) json.Unmarshal([]byte(config), &c) + c.APIToken = ctx.GetSecrets("APIToken") + max := len(search.Events) if c.Max < max { max = c.Max @@ -74,7 +76,7 @@ func (*Provider) Render(config, data string) string { } // Command handles authentication, workspace listing and items retrieval. -func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { +func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { query := r.URL.Query() method := query.Get("method") @@ -101,6 +103,10 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { config.Clean() + if config.APIToken == provider.SecretReplacement { + config.APIToken = ctx.GetSecrets("APIToken") + } + if len(config.APIToken) == 0 { provider.WriteMessage(w, me, "Missing API token") return @@ -108,14 +114,14 @@ func (p *Provider) Command(w http.ResponseWriter, r *http.Request) { switch method { case "auth": - auth(config, w, r) + auth(ctx, config, w, r) case "options": options(config, w, r) } } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) (newData string) { +func (*Provider) Refresh(ctx *provider.Context, config, data string) (newData string) { var c = papertrailConfig{} err := json.Unmarshal([]byte(config), &c) @@ -126,6 +132,8 @@ func (*Provider) Refresh(config, data string) (newData string) { c.Clean() + c.APIToken = ctx.GetSecrets("APIToken") + if len(c.APIToken) == 0 { log.Error("missing API token", err) return @@ -149,7 +157,7 @@ func (*Provider) Refresh(config, data string) (newData string) { return } -func auth(config papertrailConfig, w http.ResponseWriter, r *http.Request) { +func auth(ctx *provider.Context, config papertrailConfig, w http.ResponseWriter, r *http.Request) { result, err := fetchEvents(config) if err != nil { @@ -162,6 +170,8 @@ func auth(config papertrailConfig, w http.ResponseWriter, r *http.Request) { return } + log.IfErr(ctx.SaveSecrets(`{"APIToken":"` + config.APIToken + `"}`)) + provider.WriteJSON(w, result) } diff --git a/documize/section/provider/provider.go b/documize/section/provider/provider.go index cffd3cc3..92e40c72 100644 --- a/documize/section/provider/provider.go +++ b/documize/section/provider/provider.go @@ -19,9 +19,14 @@ import ( "sort" "strings" + "github.com/documize/community/documize/api/request" "github.com/documize/community/wordsmith/log" ) +// SecretReplacement is a constant used to replace secrets in data-structures when required. +// 8 stars. +const SecretReplacement = "********" + // sectionsMap is where individual sections register themselves. var sectionsMap = make(map[string]Provider) @@ -43,10 +48,26 @@ func (t *TypeMeta) ConfigHandle() string { // Provider represents a 'page' in a document. type Provider interface { - Meta() TypeMeta // Meta returns section details - Command(w http.ResponseWriter, r *http.Request) // Command is general-purpose method that can return data to UI - Render(config, data string) string // Render converts section data into presentable HTML - Refresh(config, data string) string // Refresh returns latest data + Meta() TypeMeta // Meta returns section details + Command(ctx *Context, w http.ResponseWriter, r *http.Request) // Command is general-purpose method that can return data to UI + Render(ctx *Context, config, data string) string // Render converts section data into presentable HTML + Refresh(ctx *Context, config, data string) string // Refresh returns latest data +} + +// Context describes the environment the section code runs in +type Context struct { + OrgID string + UserID string + prov Provider + inCommand bool +} + +// NewContext is a convenience function. +func NewContext(orgid, userid string) *Context { + if orgid == "" || userid == "" { + log.Error("NewContext incorrect orgid:"+orgid+" userid:"+userid, errors.New("bad section context")) + } + return &Context{OrgID: orgid, UserID: userid} } // Register makes document section type available @@ -71,10 +92,12 @@ func GetSectionMeta() []TypeMeta { } // Command passes parameters to the given section id, the returned bool indicates success. -func Command(section string, w http.ResponseWriter, r *http.Request) bool { +func Command(section string, ctx *Context, w http.ResponseWriter, r *http.Request) bool { s, ok := sectionsMap[section] if ok { - s.Command(w, r) + ctx.prov = s + ctx.inCommand = true + s.Command(ctx, w, r) } return ok } @@ -91,19 +114,21 @@ func Callback(section string, w http.ResponseWriter, r *http.Request) error { } // Render runs that operation for the given section id, the returned bool indicates success. -func Render(section, config, data string) (string, bool) { +func Render(section string, ctx *Context, config, data string) (string, bool) { s, ok := sectionsMap[section] if ok { - return s.Render(config, data), true + ctx.prov = s + return s.Render(ctx, config, data), true } return "", false } // Refresh returns the latest data for a section. -func Refresh(section, config, data string) (string, bool) { +func Refresh(section string, ctx *Context, config, data string) (string, bool) { s, ok := sectionsMap[section] if ok { - return s.Refresh(config, data), true + ctx.prov = s + return s.Refresh(ctx, config, data), true } return "", false } @@ -174,6 +199,28 @@ func WriteForbidden(w http.ResponseWriter) { log.IfErr(err) } +// Secrets handling + +// SaveSecrets for the current user/org combination. +// The secrets must be in the form of a JSON format string, for example `{"mysecret":"lover"}`. +// An empty string signifies no valid secrets for this user/org combination. +// Note that this function can only be called within the Command method of a section. +func (c *Context) SaveSecrets(JSONobj string) error { + if !c.inCommand { + return errors.New("SaveSecrets() may only be called from within Command()") + } + return request.UserConfigSetJSON(c.OrgID, c.UserID, c.prov.Meta().ContentType, JSONobj) +} + +// GetSecrets for the current context user/org. +// For example (see SaveSecrets example): thisContext.GetSecrets("mysecret") +// JSONpath format is defined at https://dev.mysql.com/doc/refman/5.7/en/json-path-syntax.html . +// An empty JSONpath returns the whole JSON object, as JSON. +// Errors return the empty string. +func (c *Context) GetSecrets(JSONpath string) string { + return request.UserConfigGetJSON(c.OrgID, c.UserID, c.prov.Meta().ContentType, JSONpath) +} + // sort sections in order that that should be presented. type sectionsToSort []TypeMeta diff --git a/documize/section/register.go b/documize/section/register.go index 057e937a..f75134f9 100644 --- a/documize/section/register.go +++ b/documize/section/register.go @@ -50,7 +50,6 @@ func Register() { provider.Register("trello", &trello.Provider{}) provider.Register("wysiwyg", &wysiwyg.Provider{}) provider.Register("zendesk", &zendesk.Provider{}) - p := provider.List() log.Info(fmt.Sprintf("Documize registered %d smart sections", len(p))) } diff --git a/documize/section/salesforce/salesforce.go b/documize/section/salesforce/salesforce.go index e5a165f7..9970d485 100644 --- a/documize/section/salesforce/salesforce.go +++ b/documize/section/salesforce/salesforce.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/stripe/stripe.go b/documize/section/stripe/stripe.go index cd76fedd..c646cf14 100644 --- a/documize/section/stripe/stripe.go +++ b/documize/section/stripe/stripe.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/table/table.go b/documize/section/table/table.go index 9eed6556..22cc0add 100644 --- a/documize/section/table/table.go +++ b/documize/section/table/table.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render sends back data as-is (HTML). -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/trello/trello.go b/documize/section/trello/trello.go index 72fa6f3e..32fcb6ad 100644 --- a/documize/section/trello/trello.go +++ b/documize/section/trello/trello.go @@ -45,7 +45,7 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { query := r.URL.Query() method := query.Get("method") @@ -137,7 +137,7 @@ func (*Provider) Command(w http.ResponseWriter, r *http.Request) { } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { raw := []trelloListCards{} payload := trelloRender{} var c = trelloConfig{} @@ -163,7 +163,7 @@ func (*Provider) Render(config, data string) string { } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { var c = trelloConfig{} json.Unmarshal([]byte(config), &c) diff --git a/documize/section/wysiwyg/wysiwyg.go b/documize/section/wysiwyg/wysiwyg.go index 7e801aa9..1f9ea62f 100644 --- a/documize/section/wysiwyg/wysiwyg.go +++ b/documize/section/wysiwyg/wysiwyg.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render returns data as-is (HTML). -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data } diff --git a/documize/section/zendesk/zendesk.go b/documize/section/zendesk/zendesk.go index bbfb0829..8b2d1ba5 100644 --- a/documize/section/zendesk/zendesk.go +++ b/documize/section/zendesk/zendesk.go @@ -35,16 +35,16 @@ func (*Provider) Meta() provider.TypeMeta { } // Command stub. -func (*Provider) Command(w http.ResponseWriter, r *http.Request) { +func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { provider.WriteEmpty(w) } // Render just sends back HMTL as-is. -func (*Provider) Render(config, data string) string { +func (*Provider) Render(ctx *provider.Context, config, data string) string { return data } // Refresh just sends back data as-is. -func (*Provider) Refresh(config, data string) string { +func (*Provider) Refresh(ctx *provider.Context, config, data string) string { return data }