From c3ef29ff2c4797cfaba468e201dd612950e76be9 Mon Sep 17 00:00:00 2001 From: Elliott Stoneham Date: Fri, 8 Jul 2016 20:22:30 +0100 Subject: [PATCH] User-based secrets for Gemini sections, Un/MarshallSecrets() --- .../components/section/gemini/type-editor.js | 336 +++++++++--------- documize/section/gemini/gemini.go | 30 +- documize/section/gemini/model.go | 38 +- documize/section/provider/provider.go | 30 +- 4 files changed, 261 insertions(+), 173 deletions(-) diff --git a/app/app/components/section/gemini/type-editor.js b/app/app/components/section/gemini/type-editor.js index 14a265b9..dd3babf7 100644 --- a/app/app/components/section/gemini/type-editor.js +++ b/app/app/components/section/gemini/type-editor.js @@ -15,194 +15,212 @@ import TooltipMixin from '../../../mixins/tooltip'; import SectionMixin from '../../../mixins/section'; export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, { - sectionService: Ember.inject.service('section'), - isDirty: false, - waiting: false, - authenticated: false, - user: {}, - workspaces: [], - config: {}, + sectionService: Ember.inject.service('section'), + isDirty: false, + waiting: false, + authenticated: false, + user: {}, + workspaces: [], + config: {}, - 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 = { - APIKey: "", - filter: {}, - itemCount: 0, - url: "", - userId: 0, - username: "", - workspaceId: 0, - workspaceName: "", - }; - } + if (is.empty(config)) { + config = { + APIKey: "", + filter: {}, + itemCount: 0, + url: "", + userId: 0, + username: "", + workspaceId: 0, + workspaceName: "", + }; + } - this.set('config', config); + this.set('config', config); - if (this.get('config.userId') > 0) { - this.send('auth'); - } - }, + let self = this; + self.set('waiting', true); + this.get('sectionService').fetch(this.get('page'), "secrets", this.get('config')) + .then(function (response) { + console.log(response); + self.set('waiting', false); - willDestroyElement() { - this.destroyTooltips(); - }, + self.set('config.APIKey', response.apikey); + self.set('config.url', response.url); + self.set('config.username', response.username); - getWorkspaces() { - let page = this.get('page'); - let self = this; - this.set('waiting', true); + if (response.apikey.length > 0 && response.url.length > 0 && response.username.length > 0) { + self.send('auth'); + } + }, function (reason) { //jshint ignore: line + console.log(reason); + self.set('waiting', false); + if (self.get('config.userId') > 0) { + self.send('auth'); + } + }); + }, - this.get('sectionService').fetch(page, "workspace", this.get('config')) - .then(function(response) { - // console.log(response); - let workspaceId = self.get('config.workspaceId'); + willDestroyElement() { + this.destroyTooltips(); + }, - if (response.length > 0 && workspaceId === 0) { - workspaceId = response[0].Id; - } + getWorkspaces() { + let page = this.get('page'); + let self = this; + this.set('waiting', true); - self.set("config.workspaceId", workspaceId); - self.set('workspaces', response); - self.selectWorkspace(workspaceId); + this.get('sectionService').fetch(page, "workspace", this.get('config')) + .then(function (response) { + // console.log(response); + let workspaceId = self.get('config.workspaceId'); - Ember.run.schedule('afterRender', function() { - window.scrollTo(0, document.body.scrollHeight); + if (response.length > 0 && workspaceId === 0) { + workspaceId = response[0].Id; + } - response.forEach(function(workspace) { - self.addTooltip(document.getElementById("gemini-workspace-" + workspace.Id)); - }); - }); - self.set('waiting', false); - }, function(reason) { //jshint ignore: line - self.set('workspaces', []); - self.set('waiting', false); - }); - }, + self.set("config.workspaceId", workspaceId); + self.set('workspaces', response); + self.selectWorkspace(workspaceId); - getItems() { - let page = this.get('page'); - let self = this; + Ember.run.schedule('afterRender', function () { + window.scrollTo(0, document.body.scrollHeight); - this.set('waiting', true); + response.forEach(function (workspace) { + self.addTooltip(document.getElementById("gemini-workspace-" + workspace.Id)); + }); + }); + self.set('waiting', false); + }, function (reason) { //jshint ignore: line + self.set('workspaces', []); + self.set('waiting', false); + }); + }, - this.get('sectionService').fetch(page, "items", this.get('config')) - .then(function(response) { - // console.log(response); - self.set('items', response); - self.set('config.itemCount', response.length); - self.set('waiting', false); - }, function(reason) { //jshint ignore: line - self.set('items', []); - self.set('waiting', false); - }); - }, + getItems() { + let page = this.get('page'); + let self = this; - selectWorkspace(id) { - let self = this; - let w = this.get('workspaces'); + this.set('waiting', true); - w.forEach(function(w) { - Ember.set(w, 'selected', w.Id === id); + this.get('sectionService').fetch(page, "items", this.get('config')) + .then(function (response) { + // console.log(response); + self.set('items', response); + self.set('config.itemCount', response.length); + self.set('waiting', false); + }, function (reason) { //jshint ignore: line + self.set('items', []); + self.set('waiting', false); + }); + }, - if (w.Id === id) { - self.set("config.filter", w.Filter); - self.set("config.workspaceId", id); - self.set("config.workspaceName", w.Title); - // console.log(self.get('config')); - } - }); + selectWorkspace(id) { + let self = this; + let w = this.get('workspaces'); - this.set('workspaces', w); - this.getItems(); - }, + w.forEach(function (w) { + Ember.set(w, 'selected', w.Id === id); - actions: { - isDirty() { - return this.get('isDirty'); - }, + if (w.Id === id) { + self.set("config.filter", w.Filter); + self.set("config.workspaceId", id); + self.set("config.workspaceName", w.Title); + // console.log(self.get('config')); + } + }); - auth() { - // missing data? - if (is.empty(this.get('config.url'))) { - $("#gemini-url").addClass("error").focus(); - return; - } - if (is.empty(this.get('config.username'))) { - $("#gemini-username").addClass("error").focus(); - return; - } - if (is.empty(this.get('config.APIKey'))) { - $("#gemini-apikey").addClass("error").focus(); - return; - } + this.set('workspaces', w); + this.getItems(); + }, - // knock out spaces - this.set('config.url', this.get('config.url').trim()); - this.set('config.username', this.get('config.username').trim()); - this.set('config.APIKey', this.get('config.APIKey').trim()); + actions: { + isDirty() { + return this.get('isDirty'); + }, - // remove trailing slash in URL - let url = this.get('config.url'); - if (url.indexOf("/", url.length - 1) !== -1) { - this.set('config.url', url.substring(0, url.length - 1)); - } + auth() { + // missing data? + if (is.empty(this.get('config.url'))) { + $("#gemini-url").addClass("error").focus(); + return; + } + if (is.empty(this.get('config.username'))) { + $("#gemini-username").addClass("error").focus(); + return; + } + if (is.empty(this.get('config.APIKey'))) { + $("#gemini-apikey").addClass("error").focus(); + return; + } - let page = this.get('page'); - let self = this; + // knock out spaces + this.set('config.url', this.get('config.url').trim()); + this.set('config.username', this.get('config.username').trim()); + this.set('config.APIKey', this.get('config.APIKey').trim()); - this.set('waiting', true); + // remove trailing slash in URL + let url = this.get('config.url'); + if (url.indexOf("/", url.length - 1) !== -1) { + this.set('config.url', url.substring(0, url.length - 1)); + } - this.get('sectionService').fetch(page, "auth", this.get('config')) - .then(function(response) { - self.set('authenticated', true); - self.set('user', response); - self.set('config.userId', response.BaseEntity.id); - self.set('waiting', false); - self.getWorkspaces(); - }, function(reason) { //jshint ignore: line - self.set('authenticated', false); - self.set('user', null); - self.set('config.userId', 0); - self.set('waiting', false); + let page = this.get('page'); + let self = this; - switch (reason.status) { - case 400: - self.showNotification(`Unable to connect to Gemini URL`); - break; - case 403: - self.showNotification(`Unable to authenticate`); - break; - default: - self.showNotification(`Something went wrong, try again!`); - } - }); - }, + this.set('waiting', true); - onWorkspaceChange(id) { - this.set('isDirty', true); - this.selectWorkspace(id); - }, + this.get('sectionService').fetch(page, "auth", this.get('config')) + .then(function (response) { + self.set('authenticated', true); + self.set('user', response); + self.set('config.userId', response.BaseEntity.id); + self.set('waiting', false); + self.getWorkspaces(); + }, function (reason) { //jshint ignore: line + self.set('authenticated', false); + self.set('user', null); + self.set('config.userId', 0); + self.set('waiting', false); - onCancel() { - this.attrs.onCancel(); - }, + switch (reason.status) { + case 400: + self.showNotification(`Unable to connect to Gemini URL`); + break; + case 403: + self.showNotification(`Unable to authenticate`); + break; + default: + self.showNotification(`Something went wrong, try again!`); + } + }); + }, - onAction(title) { - let page = this.get('page'); - let meta = this.get('meta'); - page.set('title', title); - meta.set('rawBody', JSON.stringify(this.get("items"))); - meta.set('config', JSON.stringify(this.get('config'))); - meta.set('externalSource', true); + onWorkspaceChange(id) { + this.set('isDirty', true); + this.selectWorkspace(id); + }, - this.attrs.onAction(page, meta); - } - } + onCancel() { + this.attrs.onCancel(); + }, + + onAction(title) { + let page = this.get('page'); + let meta = this.get('meta'); + page.set('title', title); + meta.set('rawBody', JSON.stringify(this.get("items"))); + meta.set('config', JSON.stringify(this.get('config'))); + meta.set('externalSource', true); + + this.attrs.onAction(page, meta); + } + } }); \ No newline at end of file diff --git a/documize/section/gemini/gemini.go b/documize/section/gemini/gemini.go index 8dd2a64d..83afc45f 100644 --- a/documize/section/gemini/gemini.go +++ b/documize/section/gemini/gemini.go @@ -74,12 +74,14 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R } switch method { + case "secrets": + secs(ctx, w, r) case "auth": - auth(w, r) + auth(ctx, w, r) case "workspace": - workspace(w, r) + workspace(ctx, w, r) case "items": - items(w, r) + items(ctx, w, r) } } @@ -93,7 +95,7 @@ func (*Provider) Refresh(ctx *provider.Context, config, data string) (newData st return } - c.Clean() + c.Clean(ctx) if len(c.URL) == 0 { log.Info("Gemini.Refresh received empty URL") @@ -150,7 +152,7 @@ func (*Provider) Refresh(ctx *provider.Context, config, data string) (newData st return } -func auth(w http.ResponseWriter, r *http.Request) { +func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -167,7 +169,7 @@ func auth(w http.ResponseWriter, r *http.Request) { return } - config.Clean() + config.Clean(nil) // don't look at the database for the parameters if len(config.URL) == 0 { provider.WriteMessage(w, "gemini", "Missing URL value") @@ -203,6 +205,8 @@ func auth(w http.ResponseWriter, r *http.Request) { return } + config.SaveSecrets(ctx) + defer res.Body.Close() var g = geminiUser{} @@ -218,7 +222,7 @@ func auth(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, g) } -func workspace(w http.ResponseWriter, r *http.Request) { +func workspace(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -235,7 +239,7 @@ func workspace(w http.ResponseWriter, r *http.Request) { return } - config.Clean() + config.Clean(ctx) if len(config.URL) == 0 { provider.WriteMessage(w, "gemini", "Missing URL value") @@ -291,7 +295,7 @@ func workspace(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, workspace) } -func items(w http.ResponseWriter, r *http.Request) { +func items(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -308,7 +312,7 @@ func items(w http.ResponseWriter, r *http.Request) { return } - config.Clean() + config.Clean(ctx) if len(config.URL) == 0 { provider.WriteMessage(w, "gemini", "Missing URL value") @@ -367,3 +371,9 @@ func items(w http.ResponseWriter, r *http.Request) { provider.WriteJSON(w, items) } + +func secs(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { + sec, err := getSecrets(ctx) + log.IfErr(err) + provider.WriteJSON(w, sec) +} diff --git a/documize/section/gemini/model.go b/documize/section/gemini/model.go index edf68383..84dfc1c1 100644 --- a/documize/section/gemini/model.go +++ b/documize/section/gemini/model.go @@ -11,7 +11,12 @@ package gemini -import "strings" +import ( + "strings" + + "github.com/documize/community/documize/section/provider" + "github.com/documize/community/wordsmith/log" +) // the HTML that is rendered by this section. const renderTemplate = ` @@ -82,8 +87,37 @@ type geminiConfig struct { Filter map[string]interface{} `json:"filter"` } -func (c *geminiConfig) Clean() { +func (c *geminiConfig) Clean(ctx *provider.Context) { + if ctx != nil { + sec, err := getSecrets(ctx) + if err == nil { + if len(sec.APIKey) > 0 && len(sec.Username) > 0 && len(sec.URL) > 0 { + c.APIKey = strings.TrimSpace(sec.APIKey) + c.Username = strings.TrimSpace(sec.Username) + c.URL = strings.TrimSpace(sec.URL) + } + } + } c.APIKey = strings.TrimSpace(c.APIKey) c.Username = strings.TrimSpace(c.Username) c.URL = strings.TrimSpace(c.URL) } + +func (c *geminiConfig) SaveSecrets(ctx *provider.Context) { + var sec secrets + sec.APIKey = strings.TrimSpace(c.APIKey) + sec.Username = strings.TrimSpace(c.Username) + sec.URL = strings.TrimSpace(c.URL) + log.IfErr(ctx.MarshalSecrets(sec)) +} + +type secrets struct { + URL string `json:"url"` + Username string `json:"username"` + APIKey string `json:"apikey"` +} + +func getSecrets(ctx *provider.Context) (sec secrets, err error) { + err = ctx.UnmarshalSecrets(&sec) + return +} diff --git a/documize/section/provider/provider.go b/documize/section/provider/provider.go index 7e0dc4e0..7964f856 100644 --- a/documize/section/provider/provider.go +++ b/documize/section/provider/provider.go @@ -210,7 +210,20 @@ func (c *Context) SaveSecrets(JSONobj string) error { return errors.New("SaveSecrets() may only be called from within Command()") } m := c.prov.Meta() - return request.UserConfigSetJSON(c.OrgID, c.UserID, m.ConfigHandle(), JSONobj) + return request.UserConfigSetJSON(c.OrgID, c.UserID, m.ContentType, JSONobj) +} + +// MarshalSecrets to the database. +// Parameter the same as for json.Marshal(). +func (c *Context) MarshalSecrets(sec interface{}) error { + if !c.inCommand { + return errors.New("MarshalSecrets() may only be called from within Command()") + } + byts, err := json.Marshal(sec) + if err != nil { + return err + } + return c.SaveSecrets(string(byts)) } // GetSecrets for the current context user/org. @@ -220,7 +233,20 @@ func (c *Context) SaveSecrets(JSONobj string) error { // Errors return the empty string. func (c *Context) GetSecrets(JSONpath string) string { m := c.prov.Meta() - return request.UserConfigGetJSON(c.OrgID, c.UserID, m.ConfigHandle(), JSONpath) + return request.UserConfigGetJSON(c.OrgID, c.UserID, m.ContentType, JSONpath) +} + +// ErrNoSecrets is returned if no secret is found in the database. +var ErrNoSecrets = errors.New("no secrets in database") + +// UnmarshalSecrets from the database. +// Parameter the same as for "v" in json.Unmarshal(). +func (c *Context) UnmarshalSecrets(v interface{}) error { + secTxt := c.GetSecrets("") // get all the json of the secrets + if len(secTxt) > 0 { + return json.Unmarshal([]byte(secTxt), v) + } + return ErrNoSecrets } // sort sections in order that that should be presented.