diff --git a/changelog/unreleased/siteacc-ext.md b/changelog/unreleased/siteacc-ext.md new file mode 100644 index 0000000000..1f50de4f54 --- /dev/null +++ b/changelog/unreleased/siteacc-ext.md @@ -0,0 +1,5 @@ +Enhancement: Site Accounts improvements + +The site accounts admin panel has been reworked and now also shows which sites aren't configured properly yet. Furthermore, a bug that prevented users from changing site configurations has been fixed. + +https://github.com/cs3org/reva/pull/3221 diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go deleted file mode 100644 index a1d27ac45b..0000000000 --- a/pkg/siteacc/account/panel.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018-2020 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package account - -import ( - "net/http" - "net/url" - "strings" - - "github.com/cs3org/reva/pkg/siteacc/account/contact" - "github.com/cs3org/reva/pkg/siteacc/account/edit" - "github.com/cs3org/reva/pkg/siteacc/account/login" - "github.com/cs3org/reva/pkg/siteacc/account/manage" - "github.com/cs3org/reva/pkg/siteacc/account/registration" - "github.com/cs3org/reva/pkg/siteacc/account/settings" - "github.com/cs3org/reva/pkg/siteacc/account/sites" - "github.com/cs3org/reva/pkg/siteacc/config" - "github.com/cs3org/reva/pkg/siteacc/data" - "github.com/cs3org/reva/pkg/siteacc/html" - "github.com/pkg/errors" - "github.com/rs/zerolog" -) - -// Panel represents the account panel. -type Panel struct { - html.PanelProvider - - conf *config.Configuration - - htmlPanel *html.Panel -} - -const ( - templateLogin = "login" - templateManage = "manage" - templateSettings = "settings" - templateEdit = "edit" - templateSites = "sites" - templateContact = "contact" - templateRegistration = "register" -) - -func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error { - if conf == nil { - return errors.Errorf("no configuration provided") - } - panel.conf = conf - - // Create the internal HTML panel - htmlPanel, err := html.NewPanel("account-panel", panel, conf, log) - if err != nil { - return errors.Wrap(err, "unable to create the account panel") - } - panel.htmlPanel = htmlPanel - - // Add all templates - if err := panel.htmlPanel.AddTemplate(templateLogin, &login.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the login template") - } - - if err := panel.htmlPanel.AddTemplate(templateManage, &manage.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the account management template") - } - - if err := panel.htmlPanel.AddTemplate(templateSettings, &settings.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the account settings template") - } - - if err := panel.htmlPanel.AddTemplate(templateEdit, &edit.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the account editing template") - } - - if err := panel.htmlPanel.AddTemplate(templateSites, &sites.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the sites template") - } - - if err := panel.htmlPanel.AddTemplate(templateContact, &contact.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the contact template") - } - - if err := panel.htmlPanel.AddTemplate(templateRegistration, ®istration.PanelTemplate{}); err != nil { - return errors.Wrap(err, "unable to create the registration template") - } - - return nil -} - -// GetActiveTemplate returns the name of the active template. -func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string { - validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateSites, templateContact, templateRegistration} - template := templateLogin - - // Only allow valid template paths; redirect to the login page otherwise - for _, valid := range validPaths { - if valid == path { - template = path - break - } - } - - return template -} - -// PreExecute is called before the actual template is being executed. -func (panel *Panel) PreExecute(session *html.Session, path string, w http.ResponseWriter, r *http.Request) (html.ExecutionResult, error) { - protectedPaths := []string{templateManage, templateSettings, templateEdit, templateSites, templateContact} - - if user := session.LoggedInUser(); user != nil { - switch path { - case templateSites: - // If the logged in user doesn't have sites access, redirect him back to the main account page - if !user.Account.Data.SitesAccess { - return panel.redirect(templateManage, w, r), nil - } - - case templateLogin: - case templateRegistration: - // If a user is logged in and tries to login or register again, redirect to the main account page - return panel.redirect(templateManage, w, r), nil - } - } else { - // If no user is logged in, redirect protected paths to the login page - for _, protected := range protectedPaths { - if protected == path { - return panel.redirect(templateLogin, w, r), nil - } - } - } - - return html.ContinueExecution, nil -} - -// Execute generates the HTTP output of the form and writes it to the response writer. -func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session) error { - dataProvider := func(*html.Session) interface{} { - flatValues := make(map[string]string, len(r.URL.Query())) - for k, v := range r.URL.Query() { - flatValues[strings.Title(k)] = v[0] - } - - availOps, err := data.QueryAvailableOperators(panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) - if err != nil { - return errors.Wrap(err, "unable to query available operators") - } - - type TemplateData struct { - Operator *data.Operator - Account *data.Account - Params map[string]string - - Operators []data.OperatorInformation - Sites map[string]string - Titles []string - } - - tplData := TemplateData{ - Operator: nil, - Account: nil, - Params: flatValues, - Operators: availOps, - Sites: make(map[string]string, 10), - Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, - } - if user := session.LoggedInUser(); user != nil { - availSites, err := panel.fetchAvailableSites(user.Operator) - if err != nil { - return errors.Wrap(err, "unable to query available sites") - } - - tplData.Operator = panel.cloneUserOperator(user.Operator, availSites) - tplData.Account = user.Account - tplData.Sites = availSites - } - return tplData - } - return panel.htmlPanel.Execute(w, r, session, dataProvider) -} - -func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request) html.ExecutionResult { - // Check if the original (full) URI path is stored in the request header; if not, use the request URI to get the path - fullPath := r.Header.Get("X-Replaced-Path") - if fullPath == "" { - uri, _ := url.Parse(r.RequestURI) - fullPath = uri.Path - } - - // Modify the original request URL by replacing the path parameter - newURL, _ := url.Parse(fullPath) - params := newURL.Query() - params.Del("path") - params.Add("path", path) - newURL.RawQuery = params.Encode() - http.Redirect(w, r, newURL.String(), http.StatusFound) - return html.AbortExecution -} - -func (panel *Panel) fetchAvailableSites(op *data.Operator) (map[string]string, error) { - ids, err := data.QueryOperatorSites(op.ID, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) - if err != nil { - return nil, err - } - sites := make(map[string]string, 10) - for _, id := range ids { - if siteName, _ := data.QuerySiteName(id, true, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint); err == nil { - sites[id] = siteName - } else { - sites[id] = id - } - } - return sites, nil -} - -func (panel *Panel) cloneUserOperator(op *data.Operator, sites map[string]string) *data.Operator { - // Clone the user's operator and decrypt all credentials for the panel - opClone := op.Clone(false) - for _, site := range opClone.Sites { - id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) - if err == nil { - site.Config.TestClientCredentials.ID = id - site.Config.TestClientCredentials.Secret = secret - } - } - - // Add missing sites - for id := range sites { - siteFound := false - for _, site := range opClone.Sites { - if strings.EqualFold(site.ID, id) { - siteFound = true - break - } - } - if !siteFound { - opClone.Sites = append(opClone.Sites, &data.Site{ - ID: id, - Config: data.SiteConfiguration{}, - }) - } - } - - return opClone -} - -// NewPanel creates a new account panel. -func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { - form := &Panel{} - if err := form.initialize(conf, log); err != nil { - return nil, errors.Wrap(err, "unable to initialize the account panel") - } - return form, nil -} diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go deleted file mode 100644 index d322674aad..0000000000 --- a/pkg/siteacc/admin/panel.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2018-2020 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package admin - -import ( - "net/http" - - "github.com/cs3org/reva/pkg/siteacc/config" - "github.com/cs3org/reva/pkg/siteacc/data" - "github.com/cs3org/reva/pkg/siteacc/html" - "github.com/pkg/errors" - "github.com/rs/zerolog" -) - -// Panel represents the web interface panel of the accounts service administration. -type Panel struct { - html.PanelProvider - html.ContentProvider - - htmlPanel *html.Panel -} - -const ( - templateMain = "main" -) - -func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error { - // Create the internal HTML panel - htmlPanel, err := html.NewPanel("admin-panel", panel, conf, log) - if err != nil { - return errors.Wrap(err, "unable to create the administration panel") - } - panel.htmlPanel = htmlPanel - - // Add all templates - if err := panel.htmlPanel.AddTemplate(templateMain, panel); err != nil { - return errors.Wrap(err, "unable to create the main template") - } - - return nil -} - -// GetActiveTemplate returns the name of the active template. -func (panel *Panel) GetActiveTemplate(*html.Session, string) string { - return templateMain -} - -// GetTitle returns the title of the htmlPanel. -func (panel *Panel) GetTitle() string { - return "ScienceMesh Site Administrator Accounts Panel" -} - -// GetCaption returns the caption which is displayed on the htmlPanel. -func (panel *Panel) GetCaption() string { - return "ScienceMesh Site Administrator Accounts ({{.Accounts | len}})" -} - -// GetContentJavaScript delivers additional JavaScript code. -func (panel *Panel) GetContentJavaScript() string { - return tplJavaScript -} - -// GetContentStyleSheet delivers additional stylesheet code. -func (panel *Panel) GetContentStyleSheet() string { - return tplStyleSheet -} - -// GetContentBody delivers the actual body content. -func (panel *Panel) GetContentBody() string { - return tplBody -} - -// PreExecute is called before the actual template is being executed. -func (panel *Panel) PreExecute(*html.Session, string, http.ResponseWriter, *http.Request) (html.ExecutionResult, error) { - return html.ContinueExecution, nil -} - -// Execute generates the HTTP output of the htmlPanel and writes it to the response writer. -func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session, accounts *data.Accounts) error { - dataProvider := func(*html.Session) interface{} { - type TemplateData struct { - Accounts *data.Accounts - } - - return TemplateData{ - Accounts: accounts, - } - } - return panel.htmlPanel.Execute(w, r, session, dataProvider) -} - -// NewPanel creates a new administration panel. -func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { - panel := &Panel{} - if err := panel.initialize(conf, log); err != nil { - return nil, errors.Wrap(err, "unable to initialize the administration panel") - } - return panel, nil -} diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go index da02f21d45..ac2dfe0f6d 100644 --- a/pkg/siteacc/endpoints.go +++ b/pkg/siteacc/endpoints.go @@ -71,13 +71,13 @@ func getEndpoints() []endpoint { {config.EndpointList, callMethodEndpoint, createMethodCallbacks(handleList, nil), false}, {config.EndpointFind, callMethodEndpoint, createMethodCallbacks(handleFind, nil), false}, {config.EndpointCreate, callMethodEndpoint, createMethodCallbacks(nil, handleCreate), true}, - {config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false}, - {config.EndpointConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleConfigure), false}, + {config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), true}, + {config.EndpointConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleConfigure), true}, {config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false}, // Site endpoints {config.EndpointSiteGet, callMethodEndpoint, createMethodCallbacks(handleSiteGet, nil), false}, // Sites endpoints - {config.EndpointSitesConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSitesConfigure), false}, + {config.EndpointSitesConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSitesConfigure), true}, // Login endpoints {config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), true}, {config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), true}, diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go index 3a5376bf91..de6134e23c 100644 --- a/pkg/siteacc/html/panel.go +++ b/pkg/siteacc/html/panel.go @@ -170,6 +170,10 @@ func (panel *Panel) prepareTemplate(tpl *template.Template) { } return strings.Join(sites, ", ") }, + "getSiteName": func(siteID string, fullName bool) string { + siteName, _ := data.QuerySiteName(siteID, fullName, panel.conf.Mentix.URL, panel.conf.Mentix.DataEndpoint) + return siteName + }, }) } diff --git a/pkg/siteacc/account/contact/contact.go b/pkg/siteacc/panels/account/contact/contact.go similarity index 100% rename from pkg/siteacc/account/contact/contact.go rename to pkg/siteacc/panels/account/contact/contact.go diff --git a/pkg/siteacc/account/contact/template.go b/pkg/siteacc/panels/account/contact/template.go similarity index 100% rename from pkg/siteacc/account/contact/template.go rename to pkg/siteacc/panels/account/contact/template.go diff --git a/pkg/siteacc/account/edit/edit.go b/pkg/siteacc/panels/account/edit/edit.go similarity index 100% rename from pkg/siteacc/account/edit/edit.go rename to pkg/siteacc/panels/account/edit/edit.go diff --git a/pkg/siteacc/account/edit/template.go b/pkg/siteacc/panels/account/edit/template.go similarity index 100% rename from pkg/siteacc/account/edit/template.go rename to pkg/siteacc/panels/account/edit/template.go diff --git a/pkg/siteacc/account/login/login.go b/pkg/siteacc/panels/account/login/login.go similarity index 100% rename from pkg/siteacc/account/login/login.go rename to pkg/siteacc/panels/account/login/login.go diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/panels/account/login/template.go similarity index 100% rename from pkg/siteacc/account/login/template.go rename to pkg/siteacc/panels/account/login/template.go diff --git a/pkg/siteacc/account/manage/manage.go b/pkg/siteacc/panels/account/manage/manage.go similarity index 100% rename from pkg/siteacc/account/manage/manage.go rename to pkg/siteacc/panels/account/manage/manage.go diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/panels/account/manage/template.go similarity index 100% rename from pkg/siteacc/account/manage/template.go rename to pkg/siteacc/panels/account/manage/template.go diff --git a/pkg/siteacc/panels/account/panel.go b/pkg/siteacc/panels/account/panel.go new file mode 100644 index 0000000000..634b2e0b4a --- /dev/null +++ b/pkg/siteacc/panels/account/panel.go @@ -0,0 +1,192 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package account + +import ( + "net/http" + "strings" + + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" + "github.com/cs3org/reva/pkg/siteacc/panels" + "github.com/cs3org/reva/pkg/siteacc/panels/account/contact" + "github.com/cs3org/reva/pkg/siteacc/panels/account/edit" + "github.com/cs3org/reva/pkg/siteacc/panels/account/login" + "github.com/cs3org/reva/pkg/siteacc/panels/account/manage" + "github.com/cs3org/reva/pkg/siteacc/panels/account/registration" + "github.com/cs3org/reva/pkg/siteacc/panels/account/settings" + "github.com/cs3org/reva/pkg/siteacc/panels/account/sites" + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +// Panel represents the account panel. +type Panel struct { + panels.BasePanel + html.PanelProvider +} + +const ( + templateLogin = "login" + templateManage = "manage" + templateSettings = "settings" + templateEdit = "edit" + templateSites = "sites" + templateContact = "contact" + templateRegistration = "register" +) + +func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error { + // Create templates + templates := []panels.BasePanelTemplate{ + { + ID: templateLogin, + Name: "login", + Provider: &login.PanelTemplate{}, + }, + { + ID: templateManage, + Name: "management", + Provider: &manage.PanelTemplate{}, + }, + { + ID: templateSettings, + Name: "settings", + Provider: &settings.PanelTemplate{}, + }, + { + ID: templateEdit, + Name: "editing", + Provider: &edit.PanelTemplate{}, + }, + { + ID: templateSites, + Name: "sites", + Provider: &sites.PanelTemplate{}, + }, + { + ID: templateContact, + Name: "contact", + Provider: &contact.PanelTemplate{}, + }, + { + ID: templateRegistration, + Name: "registration", + Provider: ®istration.PanelTemplate{}, + }, + } + + // Initialize base + if err := panel.BasePanel.Initialize("user-panel", panel, templates, conf, log); err != nil { + return errors.Wrap(err, "unable to create the user panel") + } + + return nil +} + +// GetActiveTemplate returns the name of the active template. +func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string { + validPaths := []string{templateLogin, templateManage, templateSettings, templateEdit, templateSites, templateContact, templateRegistration} + return panel.GetPathTemplate(validPaths, templateLogin, path) +} + +// PreExecute is called before the actual template is being executed. +func (panel *Panel) PreExecute(session *html.Session, path string, w http.ResponseWriter, r *http.Request) (html.ExecutionResult, error) { + protectedPaths := []string{templateManage, templateSettings, templateEdit, templateSites, templateContact} + + if user := session.LoggedInUser(); user != nil { + switch path { + case templateSites: + // If the logged in user doesn't have sites access, redirect him back to the main account page + if !user.Account.Data.SitesAccess { + return panel.Redirect(templateManage, w, r), nil + } + + case templateLogin: + case templateRegistration: + // If a user is logged in and tries to login or register again, redirect to the main account page + return panel.Redirect(templateManage, w, r), nil + } + } else { + // If no user is logged in, redirect protected paths to the login page + for _, protected := range protectedPaths { + if protected == path { + return panel.Redirect(templateLogin, w, r), nil + } + } + } + + return html.ContinueExecution, nil +} + +// Execute generates the HTTP output of the panel and writes it to the response writer. +func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session) error { + dataProvider := func(*html.Session) interface{} { + flatValues := make(map[string]string, len(r.URL.Query())) + for k, v := range r.URL.Query() { + flatValues[strings.Title(k)] = v[0] + } + + availOps, err := data.QueryAvailableOperators(panel.Config().Mentix.URL, panel.Config().Mentix.DataEndpoint) + if err != nil { + return errors.Wrap(err, "unable to query available operators") + } + + type TemplateData struct { + Operator *data.Operator + Account *data.Account + Params map[string]string + + Operators []data.OperatorInformation + Sites map[string]string + Titles []string + } + + tplData := TemplateData{ + Operator: nil, + Account: nil, + Params: flatValues, + Operators: availOps, + Sites: make(map[string]string, 10), + Titles: []string{"Mr", "Mrs", "Ms", "Prof", "Dr"}, + } + if user := session.LoggedInUser(); user != nil { + availSites, err := panel.FetchOperatorSites(user.Operator) + if err != nil { + return errors.Wrap(err, "unable to query available sites") + } + + tplData.Operator = panel.CloneOperator(user.Operator, availSites) + tplData.Account = user.Account + tplData.Sites = availSites + } + return tplData + } + return panel.BasePanel.Execute(w, r, session, dataProvider) +} + +// NewPanel creates a new account panel. +func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { + form := &Panel{} + if err := form.initialize(conf, log); err != nil { + return nil, errors.Wrap(err, "unable to initialize the account panel") + } + return form, nil +} diff --git a/pkg/siteacc/account/registration/registration.go b/pkg/siteacc/panels/account/registration/registration.go similarity index 100% rename from pkg/siteacc/account/registration/registration.go rename to pkg/siteacc/panels/account/registration/registration.go diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/panels/account/registration/template.go similarity index 100% rename from pkg/siteacc/account/registration/template.go rename to pkg/siteacc/panels/account/registration/template.go diff --git a/pkg/siteacc/account/settings/settings.go b/pkg/siteacc/panels/account/settings/settings.go similarity index 100% rename from pkg/siteacc/account/settings/settings.go rename to pkg/siteacc/panels/account/settings/settings.go diff --git a/pkg/siteacc/account/settings/template.go b/pkg/siteacc/panels/account/settings/template.go similarity index 100% rename from pkg/siteacc/account/settings/template.go rename to pkg/siteacc/panels/account/settings/template.go diff --git a/pkg/siteacc/account/sites/sites.go b/pkg/siteacc/panels/account/sites/sites.go similarity index 100% rename from pkg/siteacc/account/sites/sites.go rename to pkg/siteacc/panels/account/sites/sites.go diff --git a/pkg/siteacc/account/sites/template.go b/pkg/siteacc/panels/account/sites/template.go similarity index 100% rename from pkg/siteacc/account/sites/template.go rename to pkg/siteacc/panels/account/sites/template.go diff --git a/pkg/siteacc/panels/admin/accounts/accounts.go b/pkg/siteacc/panels/admin/accounts/accounts.go new file mode 100644 index 0000000000..30396e6fa2 --- /dev/null +++ b/pkg/siteacc/panels/admin/accounts/accounts.go @@ -0,0 +1,51 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package accounts + +import "github.com/cs3org/reva/pkg/siteacc/html" + +// PanelTemplate is the content provider for the contact form. +type PanelTemplate struct { + html.ContentProvider +} + +// GetTitle returns the title of the panel. +func (template *PanelTemplate) GetTitle() string { + return "ScienceMesh Site Administrator Accounts" +} + +// GetCaption returns the caption which is displayed on the panel. +func (template *PanelTemplate) GetCaption() string { + return "ScienceMesh Site Administrator Accounts" +} + +// GetContentJavaScript delivers additional JavaScript code. +func (template *PanelTemplate) GetContentJavaScript() string { + return tplJavaScript +} + +// GetContentStyleSheet delivers additional stylesheet code. +func (template *PanelTemplate) GetContentStyleSheet() string { + return tplStyleSheet +} + +// GetContentBody delivers the actual body content. +func (template *PanelTemplate) GetContentBody() string { + return tplBody +} diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/panels/admin/accounts/template.go similarity index 90% rename from pkg/siteacc/admin/template.go rename to pkg/siteacc/panels/admin/accounts/template.go index c9aa4a831c..ef142d6e84 100644 --- a/pkg/siteacc/admin/template.go +++ b/pkg/siteacc/panels/admin/accounts/template.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package admin +package accounts const tplJavaScript = ` function handleAction(action, email) { @@ -45,13 +45,19 @@ function handleAction(action, email) { const tplStyleSheet = ` html * { - font-family: monospace !important; + font-family: arial !important; +} +li::marker { + font-weight: bold; } ` -const tplBody = ` +const tplBody = ` +
+

There are currently {{.Accounts | len}} accounts stored in the system:

+
- + +
+
+

Go back to the main page.

` diff --git a/pkg/siteacc/panels/admin/manage/manage.go b/pkg/siteacc/panels/admin/manage/manage.go new file mode 100644 index 0000000000..58c64e699a --- /dev/null +++ b/pkg/siteacc/panels/admin/manage/manage.go @@ -0,0 +1,51 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package manage + +import "github.com/cs3org/reva/pkg/siteacc/html" + +// PanelTemplate is the content provider for the contact form. +type PanelTemplate struct { + html.ContentProvider +} + +// GetTitle returns the title of the panel. +func (template *PanelTemplate) GetTitle() string { + return "ScienceMesh Site Administrators Management" +} + +// GetCaption returns the caption which is displayed on the panel. +func (template *PanelTemplate) GetCaption() string { + return "ScienceMesh Site Administrators Management" +} + +// GetContentJavaScript delivers additional JavaScript code. +func (template *PanelTemplate) GetContentJavaScript() string { + return tplJavaScript +} + +// GetContentStyleSheet delivers additional stylesheet code. +func (template *PanelTemplate) GetContentStyleSheet() string { + return tplStyleSheet +} + +// GetContentBody delivers the actual body content. +func (template *PanelTemplate) GetContentBody() string { + return tplBody +} diff --git a/pkg/siteacc/panels/admin/manage/template.go b/pkg/siteacc/panels/admin/manage/template.go new file mode 100644 index 0000000000..dc88d3b585 --- /dev/null +++ b/pkg/siteacc/panels/admin/manage/template.go @@ -0,0 +1,55 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package manage + +const tplJavaScript = ` +function handleViewAccounts() { + setState(STATE_STATUS, "Redirecting to the accounts overview..."); + window.location.replace("{{getServerAddress}}/admin/?path=accounts"); +} + +function handleViewSites() { + setState(STATE_STATUS, "Redirecting to the sites overview..."); + window.location.replace("{{getServerAddress}}/admin/?path=sites"); +} +` + +const tplStyleSheet = ` +html * { + font-family: arial !important; +} +button { + min-width: 150px; +} +` + +const tplBody = ` +
+

Welcome to the ScienceMesh Site Administrators Mangement!

+

Using this service, you can manage all site administrator accounts as well as their corresponding sites.

+
+
+
+
+ + +
+
+
+` diff --git a/pkg/siteacc/panels/admin/panel.go b/pkg/siteacc/panels/admin/panel.go new file mode 100644 index 0000000000..64d3260d59 --- /dev/null +++ b/pkg/siteacc/panels/admin/panel.go @@ -0,0 +1,128 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package admin + +import ( + "net/http" + + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" + "github.com/cs3org/reva/pkg/siteacc/panels" + "github.com/cs3org/reva/pkg/siteacc/panels/admin/accounts" + "github.com/cs3org/reva/pkg/siteacc/panels/admin/manage" + "github.com/cs3org/reva/pkg/siteacc/panels/admin/sites" + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +// Panel represents the web interface panel of the accounts service administration. +type Panel struct { + panels.BasePanel + html.PanelProvider +} + +const ( + templateManage = "manage" + templateAccounts = "accounts" + templateSites = "sites" +) + +func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error { + // Create templates + templates := []panels.BasePanelTemplate{ + { + ID: templateManage, + Name: "mangement", + Provider: &manage.PanelTemplate{}, + }, + { + ID: templateAccounts, + Name: "accounts", + Provider: &accounts.PanelTemplate{}, + }, + { + ID: templateSites, + Name: "sites", + Provider: &sites.PanelTemplate{}, + }, + } + + // Initialize base + if err := panel.BasePanel.Initialize("admin-panel", panel, templates, conf, log); err != nil { + return errors.Wrap(err, "unable to create the administrator panel") + } + + return nil +} + +// GetActiveTemplate returns the name of the active template. +func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string { + validPaths := []string{templateManage, templateAccounts, templateSites} + return panel.GetPathTemplate(validPaths, templateManage, path) +} + +// PreExecute is called before the actual template is being executed. +func (panel *Panel) PreExecute(*html.Session, string, http.ResponseWriter, *http.Request) (html.ExecutionResult, error) { + return html.ContinueExecution, nil +} + +// Execute generates the HTTP output of the panel and writes it to the response writer. +func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session, accounts *data.Accounts, operators *data.Operators) error { + // Clone all operators + opsClone, err := panel.cloneOperators(operators) + if err != nil { + return errors.Wrap(err, "unable to clone operators") + } + + dataProvider := func(*html.Session) interface{} { + type TemplateData struct { + Accounts *data.Accounts + Operators *data.Operators + } + + return TemplateData{ + Accounts: accounts, + Operators: opsClone, + } + } + return panel.BasePanel.Execute(w, r, session, dataProvider) +} + +func (panel *Panel) cloneOperators(operators *data.Operators) (*data.Operators, error) { + // Clone all available operators and decrypt all credentials for the panel + opsClone := make(data.Operators, 0, len(*operators)) + for _, op := range *operators { + availSites, err := panel.FetchOperatorSites(op) + if err != nil { + return nil, errors.Wrapf(err, "unable to query available sites of operator %v", op.ID) + } + opsClone = append(opsClone, panel.CloneOperator(op, availSites)) + } + return &opsClone, nil +} + +// NewPanel creates a new administration panel. +func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) { + panel := &Panel{} + if err := panel.initialize(conf, log); err != nil { + return nil, errors.Wrap(err, "unable to initialize the administration panel") + } + return panel, nil +} diff --git a/pkg/siteacc/panels/admin/sites/sites.go b/pkg/siteacc/panels/admin/sites/sites.go new file mode 100644 index 0000000000..93e547df7d --- /dev/null +++ b/pkg/siteacc/panels/admin/sites/sites.go @@ -0,0 +1,51 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sites + +import "github.com/cs3org/reva/pkg/siteacc/html" + +// PanelTemplate is the content provider for the contact form. +type PanelTemplate struct { + html.ContentProvider +} + +// GetTitle returns the title of the panel. +func (template *PanelTemplate) GetTitle() string { + return "ScienceMesh Site Administrators Sites" +} + +// GetCaption returns the caption which is displayed on the panel. +func (template *PanelTemplate) GetCaption() string { + return "ScienceMesh Site Administrators Sites" +} + +// GetContentJavaScript delivers additional JavaScript code. +func (template *PanelTemplate) GetContentJavaScript() string { + return tplJavaScript +} + +// GetContentStyleSheet delivers additional stylesheet code. +func (template *PanelTemplate) GetContentStyleSheet() string { + return tplStyleSheet +} + +// GetContentBody delivers the actual body content. +func (template *PanelTemplate) GetContentBody() string { + return tplBody +} diff --git a/pkg/siteacc/panels/admin/sites/template.go b/pkg/siteacc/panels/admin/sites/template.go new file mode 100644 index 0000000000..93f3239bb0 --- /dev/null +++ b/pkg/siteacc/panels/admin/sites/template.go @@ -0,0 +1,67 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package sites + +const tplJavaScript = ` +` + +const tplStyleSheet = ` +html * { + font-family: arial !important; +} +li::marker { + font-weight: bold; +} +` + +const tplBody = ` +
+

There are currently {{.Operators | len}} operators stored in the system:

+
+
+
    + {{range .Operators}} +
  1. +
    +
    + {{.ID}} ({{.Sites | len}} site(s))
    +
    +
    +
      + {{$parent := .}} + {{range .Sites}} +
    • + {{getSiteName .ID true}} ({{.ID}})
      + {{if not .Config.TestClientCredentials.ID}} + Test user not configured! + {{end}} +
    • + {{end}} +
    +
    +
    +
    +
  2. + {{end}} +
+
+
+

Go back to the main page.

+
+` diff --git a/pkg/siteacc/panels/basepanel.go b/pkg/siteacc/panels/basepanel.go new file mode 100644 index 0000000000..e7ff5e326d --- /dev/null +++ b/pkg/siteacc/panels/basepanel.go @@ -0,0 +1,162 @@ +// Copyright 2018-2020 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package panels + +import ( + "net/http" + "net/url" + "strings" + + "github.com/cs3org/reva/pkg/siteacc/config" + "github.com/cs3org/reva/pkg/siteacc/data" + "github.com/cs3org/reva/pkg/siteacc/html" + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +// BasePanel represents the base for all panels. +type BasePanel struct { + conf *config.Configuration + + htmlPanel *html.Panel +} + +// BasePanelTemplate represents an HTML template used for initialization. +type BasePanelTemplate struct { + ID string + Name string + Provider html.ContentProvider +} + +// Initialize initializes the base panel. +func (panel *BasePanel) Initialize(name string, panelProvider html.PanelProvider, templates []BasePanelTemplate, conf *config.Configuration, log *zerolog.Logger) error { + if conf == nil { + return errors.Errorf("no configuration provided") + } + panel.conf = conf + + // Create the internal HTML panel + htmlPanel, err := html.NewPanel(name, panelProvider, conf, log) + if err != nil { + return errors.Wrap(err, "unable to create the HTML panel") + } + panel.htmlPanel = htmlPanel + + // Add all templates + for _, template := range templates { + if err := panel.htmlPanel.AddTemplate(template.ID, template.Provider); err != nil { + return errors.Wrapf(err, "unable to create the %v template", template.Name) + } + } + + return nil +} + +// GetPathTemplate returns the name of the active template. +func (panel *BasePanel) GetPathTemplate(validPaths []string, defaultTemplate string, path string) string { + template := defaultTemplate + + // Only allow valid template paths; redirect to the default template otherwise + for _, valid := range validPaths { + if valid == path { + template = path + break + } + } + + return template +} + +// Execute generates the HTTP output of the panel and writes it to the response writer. +func (panel *BasePanel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session, dataProvider html.PanelDataProvider) error { + return panel.htmlPanel.Execute(w, r, session, dataProvider) +} + +// Redirect performs an HTTP redirect. +func (panel *BasePanel) Redirect(path string, w http.ResponseWriter, r *http.Request) html.ExecutionResult { + // Check if the original (full) URI path is stored in the request header; if not, use the request URI to get the path + fullPath := r.Header.Get("X-Replaced-Path") + if fullPath == "" { + uri, _ := url.Parse(r.RequestURI) + fullPath = uri.Path + } + + // Modify the original request URL by replacing the path parameter + newURL, _ := url.Parse(fullPath) + params := newURL.Query() + params.Del("path") + params.Add("path", path) + newURL.RawQuery = params.Encode() + http.Redirect(w, r, newURL.String(), http.StatusFound) + return html.AbortExecution +} + +// FetchOperatorSites fetches all sites for an operator using Mentix. +func (panel *BasePanel) FetchOperatorSites(op *data.Operator) (map[string]string, error) { + ids, err := data.QueryOperatorSites(op.ID, panel.Config().Mentix.URL, panel.Config().Mentix.DataEndpoint) + if err != nil { + return nil, err + } + sites := make(map[string]string, 10) + for _, id := range ids { + if siteName, _ := data.QuerySiteName(id, true, panel.Config().Mentix.URL, panel.Config().Mentix.DataEndpoint); err == nil { + sites[id] = siteName + } else { + sites[id] = id + } + } + return sites, nil +} + +// CloneOperator clones an operator and adds missing sites. +func (panel *BasePanel) CloneOperator(op *data.Operator, sites map[string]string) *data.Operator { + // Clone the operator and decrypt all credentials for the panel + opClone := op.Clone(false) + for _, site := range opClone.Sites { + id, secret, err := site.Config.TestClientCredentials.Get(panel.conf.Security.CredentialsPassphrase) + if err == nil { + site.Config.TestClientCredentials.ID = id + site.Config.TestClientCredentials.Secret = secret + } + } + + // Add missing sites + for id := range sites { + siteFound := false + for _, site := range opClone.Sites { + if strings.EqualFold(site.ID, id) { + siteFound = true + break + } + } + if !siteFound { + opClone.Sites = append(opClone.Sites, &data.Site{ + ID: id, + Config: data.SiteConfiguration{}, + }) + } + } + + return opClone +} + +// Config gets the configuration object. +func (panel *BasePanel) Config() *config.Configuration { + return panel.conf +} diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go index f099f4dac5..569e0a978f 100644 --- a/pkg/siteacc/siteacc.go +++ b/pkg/siteacc/siteacc.go @@ -22,13 +22,13 @@ import ( "fmt" "net/http" - accpanel "github.com/cs3org/reva/pkg/siteacc/account" - "github.com/cs3org/reva/pkg/siteacc/admin" "github.com/cs3org/reva/pkg/siteacc/alerting" "github.com/cs3org/reva/pkg/siteacc/config" "github.com/cs3org/reva/pkg/siteacc/data" "github.com/cs3org/reva/pkg/siteacc/html" "github.com/cs3org/reva/pkg/siteacc/manager" + accpanel "github.com/cs3org/reva/pkg/siteacc/panels/account" + "github.com/cs3org/reva/pkg/siteacc/panels/admin" "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -154,7 +154,8 @@ func (siteacc *SiteAccounts) RequestHandler() http.Handler { func (siteacc *SiteAccounts) ShowAdministrationPanel(w http.ResponseWriter, r *http.Request, session *html.Session) error { // The admin panel only shows the stored accounts and offers actions through links, so let it use cloned data accounts := siteacc.accountsManager.CloneAccounts(true) - return siteacc.adminPanel.Execute(w, r, session, &accounts) + operators := siteacc.operatorsManager.CloneOperators(false) + return siteacc.adminPanel.Execute(w, r, session, &accounts, &operators) } // ShowAccountPanel writes the account panel HTTP output directly to the response writer.