From fa71253552e41de808f2b4dc7e591407cec0322a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 30 Jun 2021 12:50:50 +0200
Subject: [PATCH 01/60] Add Dockerfile.old
---
Dockerfile.old | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 Dockerfile.old
diff --git a/Dockerfile.old b/Dockerfile.old
new file mode 100644
index 0000000000..82c4db78e3
--- /dev/null
+++ b/Dockerfile.old
@@ -0,0 +1,26 @@
+# Copyright 2018-2021 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.
+
+FROM golang:1.16
+
+WORKDIR /go/src/github/cs3org/reva
+COPY . .
+RUN make build-revad-docker && cp /go/src/github/cs3org/reva/cmd/revad/revad /go/bin/revad && mkdir -p /etc/revad/ && echo "" > /etc/revad/revad.toml
+EXPOSE 9999
+EXPOSE 10000
+CMD ["/go/bin/revad", "-c", "/etc/revad/revad.toml", "-p", "/var/run/revad.pid"]
From 20128434aff8a211e8c04d6937984c6f2091cd4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 30 Jun 2021 14:21:52 +0200
Subject: [PATCH 02/60] Restructure endpoint handling
---
.../publicstorageprovider.go | 2 +-
.../cloud/capabilities/capabilities.go | 22 +-
internal/http/services/siteacc/siteacc.go | 307 +----------------
.../services => pkg}/siteacc/config/config.go | 0
.../siteacc/config/endpoints.go | 0
.../services => pkg}/siteacc/data/account.go | 0
.../siteacc/data/filestorage.go | 9 +-
.../services => pkg}/siteacc/data/storage.go | 0
.../services => pkg}/siteacc/email/email.go | 8 +-
.../siteacc/email/template.go | 0
pkg/siteacc/endpoints.go | 322 ++++++++++++++++++
.../http/services => pkg}/siteacc/manager.go | 78 ++---
.../services => pkg}/siteacc/panel/panel.go | 15 +-
.../siteacc/panel/template.go | 0
.../siteacc/registration/form.go | 9 +-
.../siteacc/registration/template.go | 0
pkg/siteacc/siteacc.go | 101 ++++++
.../siteacc/sitereg/sitereg.go | 0
18 files changed, 501 insertions(+), 372 deletions(-)
rename {internal/http/services => pkg}/siteacc/config/config.go (100%)
rename {internal/http/services => pkg}/siteacc/config/endpoints.go (100%)
rename {internal/http/services => pkg}/siteacc/data/account.go (100%)
rename {internal/http/services => pkg}/siteacc/data/filestorage.go (91%)
rename {internal/http/services => pkg}/siteacc/data/storage.go (100%)
rename {internal/http/services => pkg}/siteacc/email/email.go (84%)
rename {internal/http/services => pkg}/siteacc/email/template.go (100%)
create mode 100644 pkg/siteacc/endpoints.go
rename {internal/http/services => pkg}/siteacc/manager.go (77%)
rename {internal/http/services => pkg}/siteacc/panel/panel.go (83%)
rename {internal/http/services => pkg}/siteacc/panel/template.go (100%)
rename {internal/http/services => pkg}/siteacc/registration/form.go (87%)
rename {internal/http/services => pkg}/siteacc/registration/template.go (100%)
create mode 100644 pkg/siteacc/siteacc.go
rename {internal/http/services => pkg}/siteacc/sitereg/sitereg.go (100%)
diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go
index b53f5adf07..0a1f9e7e70 100644
--- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go
+++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go
@@ -78,7 +78,7 @@ func parseConfig(m map[string]interface{}) (*config, error) {
return c, nil
}
-// New creates a new Public Storage Provider service.
+// New creates a new IsPublic Storage Provider service.
func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) {
c, err := parseConfig(m)
if err != nil {
diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go b/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go
index 7b3b0d21df..6243445fa7 100644
--- a/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go
+++ b/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go
@@ -128,7 +128,7 @@ func (h *Handler) Init(c *config.Config) {
h.c.Capabilities.FilesSharing.Public = &data.CapabilitiesFilesSharingPublic{}
}
- // h.c.Capabilities.FilesSharing.Public.Enabled is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Enabled is boolean
h.c.Capabilities.FilesSharing.Public.Enabled = true
if h.c.Capabilities.FilesSharing.Public.Password == nil {
@@ -139,22 +139,22 @@ func (h *Handler) Init(c *config.Config) {
h.c.Capabilities.FilesSharing.Public.Password.EnforcedFor = &data.CapabilitiesFilesSharingPublicPasswordEnforcedFor{}
}
- // h.c.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadOnly is boolean
- // h.c.Capabilities.FilesSharing.Public.Password.EnforcedFor.ReadWrite is boolean
- // h.c.Capabilities.FilesSharing.Public.Password.EnforcedFor.UploadOnly is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Password.EnforcedFor.ReadOnly is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Password.EnforcedFor.ReadWrite is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Password.EnforcedFor.UploadOnly is boolean
- // h.c.Capabilities.FilesSharing.Public.Password.Enforced is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Password.Enforced is boolean
if h.c.Capabilities.FilesSharing.Public.ExpireDate == nil {
h.c.Capabilities.FilesSharing.Public.ExpireDate = &data.CapabilitiesFilesSharingPublicExpireDate{}
}
- // h.c.Capabilities.FilesSharing.Public.ExpireDate.Enabled is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.ExpireDate.Enabled is boolean
- // h.c.Capabilities.FilesSharing.Public.SendMail is boolean
- // h.c.Capabilities.FilesSharing.Public.SocialShare is boolean
- // h.c.Capabilities.FilesSharing.Public.Upload is boolean
- // h.c.Capabilities.FilesSharing.Public.Multiple is boolean
- // h.c.Capabilities.FilesSharing.Public.SupportsUploadOnly is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.SendMail is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.SocialShare is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Upload is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.Multiple is boolean
+ // h.c.Capabilities.FilesSharing.IsPublic.SupportsUploadOnly is boolean
if h.c.Capabilities.FilesSharing.User == nil {
h.c.Capabilities.FilesSharing.User = &data.CapabilitiesFilesSharingUser{}
diff --git a/internal/http/services/siteacc/siteacc.go b/internal/http/services/siteacc/siteacc.go
index e3f5765d39..a57c92cd98 100644
--- a/internal/http/services/siteacc/siteacc.go
+++ b/internal/http/services/siteacc/siteacc.go
@@ -19,20 +19,14 @@
package siteacc
import (
- "encoding/json"
- "fmt"
- "io/ioutil"
"net/http"
- "net/url"
- "strings"
+ "github.com/cs3org/reva/pkg/siteacc"
+ "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/rs/zerolog"
- "github.com/cs3org/reva/internal/http/services/siteacc/config"
- "github.com/cs3org/reva/internal/http/services/siteacc/data"
- "github.com/cs3org/reva/pkg/mentix/key"
"github.com/cs3org/reva/pkg/rhttp/global"
)
@@ -44,7 +38,7 @@ type svc struct {
conf *config.Configuration
log *zerolog.Logger
- manager *Manager
+ siteacc *siteacc.SiteAccounts
}
const (
@@ -63,297 +57,12 @@ func (s *svc) Prefix() string {
// Unprotected returns all endpoints that can be queried without prior authorization.
func (s *svc) Unprotected() []string {
- // The account creation endpoint is always unprotected
- endpoints := []string{config.EndpointCreate}
-
- // If enabled, the registration registrationForm endpoint is also unprotected
- if s.conf.EnableRegistrationForm {
- endpoints = append(endpoints, config.EndpointRegistration)
- }
-
- return endpoints
+ return s.siteacc.GetPublicEndpoints()
}
// Handler serves all HTTP requests.
func (s *svc) Handler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- defer r.Body.Close()
-
- switch r.URL.Path {
- case config.EndpointPanel:
- s.handlePanelEndpoint(w, r)
-
- case config.EndpointRegistration:
- if s.conf.EnableRegistrationForm {
- s.handleRegistrationEndpoint(w, r)
- }
-
- default:
- s.handleRequestEndpoints(w, r)
- }
- })
-}
-
-func (s *svc) handlePanelEndpoint(w http.ResponseWriter, r *http.Request) {
- if err := s.manager.ShowPanel(w); err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface panel: %v", err)))
- }
-}
-
-func (s *svc) handleRegistrationEndpoint(w http.ResponseWriter, r *http.Request) {
- if err := s.manager.ShowRegistrationForm(w); err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface registration registrationForm: %v", err)))
- }
-}
-
-func (s *svc) handleRequestEndpoints(w http.ResponseWriter, r *http.Request) {
- // Allow definition of endpoints in a flexible and easy way
- type Endpoint struct {
- Path string
- Method string
- Handler func(url.Values, []byte) (interface{}, error)
- }
-
- // Every request to the accounts service results in a standardized JSON response
- type Response struct {
- Success bool `json:"success"`
- Error string `json:"error,omitempty"`
- Data interface{} `json:"data,omitempty"`
- }
-
- endpoints := []Endpoint{
- {config.EndpointGenerateAPIKey, http.MethodGet, s.handleGenerateAPIKey},
- {config.EndpointVerifyAPIKey, http.MethodGet, s.handleVerifyAPIKey},
- {config.EndpointAssignAPIKey, http.MethodPost, s.handleAssignAPIKey},
- {config.EndpointList, http.MethodGet, s.handleList},
- {config.EndpointFind, http.MethodGet, s.handleFind},
- {config.EndpointCreate, http.MethodPost, s.handleCreate},
- {config.EndpointUpdate, http.MethodPost, s.handleUpdate},
- {config.EndpointRemove, http.MethodPost, s.handleRemove},
- {config.EndpointAuthorize, http.MethodPost, s.handleAuthorize},
- {config.EndpointIsAuthorized, http.MethodGet, s.handleIsAuthorized},
- {config.EndpointUnregisterSite, http.MethodPost, s.handleUnregisterSite},
- }
-
- // The default response is an unknown endpoint (for the specified method)
- resp := Response{
- Success: false,
- Error: fmt.Sprintf("unknown endpoint %v for method %v", r.URL.Path, r.Method),
- Data: nil,
- }
-
- // Check each endpoint if it can handle the request
- for _, endpoint := range endpoints {
- if r.URL.Path == endpoint.Path && r.Method == endpoint.Method {
- body, _ := ioutil.ReadAll(r.Body)
-
- if data, err := endpoint.Handler(r.URL.Query(), body); err == nil {
- resp.Success = true
- resp.Error = ""
- resp.Data = data
- } else {
- resp.Success = false
- resp.Error = fmt.Sprintf("%v", err)
- resp.Data = nil
- }
-
- break
- }
- }
-
- // Any failure during query handling results in a bad request
- if !resp.Success {
- w.WriteHeader(http.StatusBadRequest)
- }
-
- jsonData, _ := json.MarshalIndent(&resp, "", "\t")
- _, _ = w.Write(jsonData)
-}
-
-func (s *svc) handleGenerateAPIKey(values url.Values, body []byte) (interface{}, error) {
- email := values.Get("email")
- flags := key.FlagDefault
-
- if strings.EqualFold(values.Get("isScienceMesh"), "true") {
- flags |= key.FlagScienceMesh
- }
-
- if len(email) == 0 {
- return nil, errors.Errorf("no email provided")
- }
-
- apiKey, err := key.GenerateAPIKey(key.SaltFromEmail(email), flags)
- if err != nil {
- return nil, errors.Wrap(err, "unable to generate API key")
- }
- return map[string]string{"apiKey": apiKey}, nil
-}
-
-func (s *svc) handleVerifyAPIKey(values url.Values, body []byte) (interface{}, error) {
- apiKey := values.Get("apiKey")
- email := values.Get("email")
-
- if len(apiKey) == 0 {
- return nil, errors.Errorf("no API key provided")
- }
-
- if len(email) == 0 {
- return nil, errors.Errorf("no email provided")
- }
-
- err := key.VerifyAPIKey(apiKey, key.SaltFromEmail(email))
- if err != nil {
- return nil, errors.Wrap(err, "invalid API key")
- }
- return nil, nil
-}
-
-func (s *svc) handleAssignAPIKey(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- flags := key.FlagDefault
- if _, ok := values["isScienceMesh"]; ok {
- flags |= key.FlagScienceMesh
- }
-
- // Assign a new API key to the account through the account manager
- if err := s.manager.AssignAPIKeyToAccount(account, flags); err != nil {
- return nil, errors.Wrap(err, "unable to assign API key")
- }
-
- return nil, nil
-}
-
-func (s *svc) handleList(values url.Values, body []byte) (interface{}, error) {
- return s.manager.CloneAccounts(), nil
-}
-
-func (s *svc) handleFind(values url.Values, body []byte) (interface{}, error) {
- account, err := s.findAccount(values.Get("by"), values.Get("value"))
- if err != nil {
- return nil, err
- }
- return map[string]interface{}{"account": account}, nil
-}
-
-func (s *svc) handleCreate(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- // Create a new account through the account manager
- if err := s.manager.CreateAccount(account); err != nil {
- return nil, errors.Wrap(err, "unable to create account")
- }
-
- return nil, nil
-}
-
-func (s *svc) handleUpdate(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- // Update the account through the account manager; only the basic data of an account can be updated through this endpoint
- if err := s.manager.UpdateAccount(account, false); err != nil {
- return nil, errors.Wrap(err, "unable to update account")
- }
-
- return nil, nil
-}
-
-func (s *svc) handleRemove(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- // Remove the account through the account manager
- if err := s.manager.RemoveAccount(account); err != nil {
- return nil, errors.Wrap(err, "unable to remove account")
- }
-
- return nil, nil
-}
-
-func (s *svc) handleIsAuthorized(values url.Values, body []byte) (interface{}, error) {
- account, err := s.findAccount(values.Get("by"), values.Get("value"))
- if err != nil {
- return nil, err
- }
- return account.Data.Authorized, nil
-}
-
-func (s *svc) handleUnregisterSite(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- // Unregister the account's site through the account manager
- if err := s.manager.UnregisterAccountSite(account); err != nil {
- return nil, errors.Wrap(err, "unable to unregister the site of the given account")
- }
-
- return nil, nil
-}
-
-func (s *svc) handleAuthorize(values url.Values, body []byte) (interface{}, error) {
- account, err := s.unmarshalRequestData(body)
- if err != nil {
- return nil, err
- }
-
- if val := values.Get("status"); len(val) > 0 {
- var authorize bool
- switch strings.ToLower(val) {
- case "true":
- authorize = true
-
- case "false":
- authorize = false
-
- default:
- return nil, errors.Errorf("unsupported authorization status %v", val[0])
- }
-
- // Authorize the account through the account manager
- if err := s.manager.AuthorizeAccount(account, authorize); err != nil {
- return nil, errors.Wrap(err, "unable to (un)authorize account")
- }
- } else {
- return nil, errors.Errorf("no authorization status provided")
- }
-
- return nil, nil
-}
-
-func (s *svc) unmarshalRequestData(body []byte) (*data.Account, error) {
- account := &data.Account{}
- if err := json.Unmarshal(body, account); err != nil {
- return nil, errors.Wrap(err, "invalid account data")
- }
- return account, nil
-}
-
-func (s *svc) findAccount(by string, value string) (*data.Account, error) {
- if len(by) == 0 && len(value) == 0 {
- return nil, errors.Errorf("missing search criteria")
- }
-
- // Find the account using the account manager
- account, err := s.manager.FindAccount(by, value)
- if err != nil {
- return nil, errors.Wrap(err, "user not found")
- }
- return account, nil
+ return s.siteacc.RequestHandler()
}
func parseConfig(m map[string]interface{}) (*config.Configuration, error) {
@@ -383,8 +92,8 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)
return nil, err
}
- // Create the accounts manager instance
- mngr, err := newManager(conf, log)
+ // Create the site accounts instance
+ siteacc, err := siteacc.New(conf, log)
if err != nil {
return nil, errors.Wrap(err, "error creating the site accounts service")
}
@@ -393,7 +102,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)
s := &svc{
conf: conf,
log: log,
- manager: mngr,
+ siteacc: siteacc,
}
return s, nil
}
diff --git a/internal/http/services/siteacc/config/config.go b/pkg/siteacc/config/config.go
similarity index 100%
rename from internal/http/services/siteacc/config/config.go
rename to pkg/siteacc/config/config.go
diff --git a/internal/http/services/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
similarity index 100%
rename from internal/http/services/siteacc/config/endpoints.go
rename to pkg/siteacc/config/endpoints.go
diff --git a/internal/http/services/siteacc/data/account.go b/pkg/siteacc/data/account.go
similarity index 100%
rename from internal/http/services/siteacc/data/account.go
rename to pkg/siteacc/data/account.go
diff --git a/internal/http/services/siteacc/data/filestorage.go b/pkg/siteacc/data/filestorage.go
similarity index 91%
rename from internal/http/services/siteacc/data/filestorage.go
rename to pkg/siteacc/data/filestorage.go
index 22efb99b3e..dbc788868c 100644
--- a/internal/http/services/siteacc/data/filestorage.go
+++ b/pkg/siteacc/data/filestorage.go
@@ -24,23 +24,22 @@ import (
"os"
"path/filepath"
+ config2 "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/pkg/errors"
"github.com/rs/zerolog"
-
- "github.com/cs3org/reva/internal/http/services/siteacc/config"
)
// FileStorage implements a filePath-based storage.
type FileStorage struct {
Storage
- conf *config.Configuration
+ conf *config2.Configuration
log *zerolog.Logger
filePath string
}
-func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (storage *FileStorage) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -107,7 +106,7 @@ func (storage *FileStorage) AccountRemoved(account *Account) {
}
// NewFileStorage creates a new filePath storage.
-func NewFileStorage(conf *config.Configuration, log *zerolog.Logger) (*FileStorage, error) {
+func NewFileStorage(conf *config2.Configuration, log *zerolog.Logger) (*FileStorage, error) {
storage := &FileStorage{}
if err := storage.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the filePath storage")
diff --git a/internal/http/services/siteacc/data/storage.go b/pkg/siteacc/data/storage.go
similarity index 100%
rename from internal/http/services/siteacc/data/storage.go
rename to pkg/siteacc/data/storage.go
diff --git a/internal/http/services/siteacc/email/email.go b/pkg/siteacc/email/email.go
similarity index 84%
rename from internal/http/services/siteacc/email/email.go
rename to pkg/siteacc/email/email.go
index 72e15f3c66..1756cdf1b5 100644
--- a/internal/http/services/siteacc/email/email.go
+++ b/pkg/siteacc/email/email.go
@@ -22,24 +22,24 @@ import (
"bytes"
"text/template"
+ data2 "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/pkg/errors"
- "github.com/cs3org/reva/internal/http/services/siteacc/data"
"github.com/cs3org/reva/pkg/smtpclient"
)
// SendAccountCreated sends an email about account creation.
-func SendAccountCreated(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAccountCreated(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, account, smtp)
}
// SendAPIKeyAssigned sends an email about API key assignment.
-func SendAPIKeyAssigned(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAPIKeyAssigned(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, account, smtp)
}
// SendAccountAuthorized sends an email about account authorization.
-func SendAccountAuthorized(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAccountAuthorized(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, account, smtp)
}
diff --git a/internal/http/services/siteacc/email/template.go b/pkg/siteacc/email/template.go
similarity index 100%
rename from internal/http/services/siteacc/email/template.go
rename to pkg/siteacc/email/template.go
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
new file mode 100644
index 0000000000..0979be02d5
--- /dev/null
+++ b/pkg/siteacc/endpoints.go
@@ -0,0 +1,322 @@
+// 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 siteacc
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/cs3org/reva/pkg/mentix/key"
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/data"
+ "github.com/pkg/errors"
+)
+
+type methodCallback = func(*Manager, url.Values, []byte) (interface{}, error)
+
+type endpoint struct {
+ Path string
+ Handler func(*Manager, endpoint, http.ResponseWriter, *http.Request)
+ MethodCallbacks map[string]methodCallback
+ IsPublic bool
+}
+
+func createMethodCallbacks(cbGet methodCallback, cbPost methodCallback) map[string]methodCallback {
+ callbacks := make(map[string]methodCallback)
+
+ if cbGet != nil {
+ callbacks[http.MethodGet] = cbGet
+ }
+
+ if cbPost != nil {
+ callbacks[http.MethodPost] = cbPost
+ }
+
+ return callbacks
+}
+
+func getEndpoints(enableRegistrationForm bool) []endpoint {
+ endpoints := []endpoint{
+ // Form endpoints
+ {config.EndpointPanel, handlePanelEndpoint, nil, false},
+ // Request endpoints
+ {config.EndpointGenerateAPIKey, handleMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
+ {config.EndpointVerifyAPIKey, handleMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
+ {config.EndpointAssignAPIKey, handleMethodEndpoint, createMethodCallbacks(nil, handleAssignAPIKey), false},
+ {config.EndpointList, handleMethodEndpoint, createMethodCallbacks(handleList, nil), false},
+ {config.EndpointFind, handleMethodEndpoint, createMethodCallbacks(handleFind, nil), false},
+ {config.EndpointCreate, handleMethodEndpoint, createMethodCallbacks(nil, handleCreate), true},
+ {config.EndpointUpdate, handleMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false},
+ {config.EndpointRemove, handleMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
+ {config.EndpointAuthorize, handleMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
+ {config.EndpointIsAuthorized, handleMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
+ {config.EndpointUnregisterSite, handleMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false},
+ }
+
+ if enableRegistrationForm {
+ endpoints = append(endpoints, endpoint{config.EndpointRegistration, handleRegistrationEndpoint, nil, true})
+ }
+
+ return endpoints
+}
+
+func handlePanelEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+ if err := mngr.ShowPanel(w); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface panel: %v", err)))
+ }
+}
+
+func handleRegistrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+ if err := mngr.ShowRegistrationForm(w); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface registration registrationForm: %v", err)))
+ }
+}
+
+func handleMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+ // Every request to the accounts service results in a standardized JSON response
+ type Response struct {
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+ Data interface{} `json:"data,omitempty"`
+ }
+
+ // The default response is an unknown requestHandler (for the specified method)
+ resp := Response{
+ Success: false,
+ Error: fmt.Sprintf("unknown endpoint %v for method %v", r.URL.Path, r.Method),
+ Data: nil,
+ }
+
+ if ep.MethodCallbacks != nil {
+ // Search for a matching method in the list of callbacks
+ for method, cb := range ep.MethodCallbacks {
+ if method == r.Method {
+ body, _ := ioutil.ReadAll(r.Body)
+
+ if respData, err := cb(mngr, r.URL.Query(), body); err == nil {
+ resp.Success = true
+ resp.Error = ""
+ resp.Data = respData
+ } else {
+ resp.Success = false
+ resp.Error = fmt.Sprintf("%v", err)
+ resp.Data = nil
+ }
+ }
+ }
+ }
+
+ // Any failure during query handling results in a bad request
+ if !resp.Success {
+ w.WriteHeader(http.StatusBadRequest)
+ }
+
+ jsonData, _ := json.MarshalIndent(&resp, "", "\t")
+ _, _ = w.Write(jsonData)
+}
+
+func handleGenerateAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ email := values.Get("email")
+ flags := key.FlagDefault
+
+ if strings.EqualFold(values.Get("isScienceMesh"), "true") {
+ flags |= key.FlagScienceMesh
+ }
+
+ if len(email) == 0 {
+ return nil, errors.Errorf("no email provided")
+ }
+
+ apiKey, err := key.GenerateAPIKey(key.SaltFromEmail(email), flags)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to generate API key")
+ }
+ return map[string]string{"apiKey": apiKey}, nil
+}
+
+func handleVerifyAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ apiKey := values.Get("apiKey")
+ email := values.Get("email")
+
+ if len(apiKey) == 0 {
+ return nil, errors.Errorf("no API key provided")
+ }
+
+ if len(email) == 0 {
+ return nil, errors.Errorf("no email provided")
+ }
+
+ err := key.VerifyAPIKey(apiKey, key.SaltFromEmail(email))
+ if err != nil {
+ return nil, errors.Wrap(err, "invalid API key")
+ }
+ return nil, nil
+}
+
+func handleAssignAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ flags := key.FlagDefault
+ if _, ok := values["isScienceMesh"]; ok {
+ flags |= key.FlagScienceMesh
+ }
+
+ // Assign a new API key to the account through the account manager
+ if err := mngr.AssignAPIKeyToAccount(account, flags); err != nil {
+ return nil, errors.Wrap(err, "unable to assign API key")
+ }
+
+ return nil, nil
+}
+
+func handleList(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ return mngr.CloneAccounts(), nil
+}
+
+func handleFind(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := findAccount(mngr, values.Get("by"), values.Get("value"))
+ if err != nil {
+ return nil, err
+ }
+ return map[string]interface{}{"account": account}, nil
+}
+
+func handleCreate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create a new account through the account manager
+ if err := mngr.CreateAccount(account); err != nil {
+ return nil, errors.Wrap(err, "unable to create account")
+ }
+
+ return nil, nil
+}
+
+func handleUpdate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Update the account through the account manager; only the basic data of an account can be updated through this requestHandler
+ if err := mngr.UpdateAccount(account, false); err != nil {
+ return nil, errors.Wrap(err, "unable to update account")
+ }
+
+ return nil, nil
+}
+
+func handleRemove(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Remove the account through the account manager
+ if err := mngr.RemoveAccount(account); err != nil {
+ return nil, errors.Wrap(err, "unable to remove account")
+ }
+
+ return nil, nil
+}
+
+func handleIsAuthorized(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := findAccount(mngr, values.Get("by"), values.Get("value"))
+ if err != nil {
+ return nil, err
+ }
+ return account.Data.Authorized, nil
+}
+
+func handleUnregisterSite(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Unregister the account's site through the account manager
+ if err := mngr.UnregisterAccountSite(account); err != nil {
+ return nil, errors.Wrap(err, "unable to unregister the site of the given account")
+ }
+
+ return nil, nil
+}
+
+func handleAuthorize(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ if val := values.Get("status"); len(val) > 0 {
+ var authorize bool
+ switch strings.ToLower(val) {
+ case "true":
+ authorize = true
+
+ case "false":
+ authorize = false
+
+ default:
+ return nil, errors.Errorf("unsupported authorization status %v", val[0])
+ }
+
+ // Authorize the account through the account manager
+ if err := mngr.AuthorizeAccount(account, authorize); err != nil {
+ return nil, errors.Wrap(err, "unable to (un)authorize account")
+ }
+ } else {
+ return nil, errors.Errorf("no authorization status provided")
+ }
+
+ return nil, nil
+}
+
+func unmarshalRequestData(body []byte) (*data.Account, error) {
+ account := &data.Account{}
+ if err := json.Unmarshal(body, account); err != nil {
+ return nil, errors.Wrap(err, "invalid account data")
+ }
+ return account, nil
+}
+
+func findAccount(mngr *Manager, by string, value string) (*data.Account, error) {
+ if len(by) == 0 && len(value) == 0 {
+ return nil, errors.Errorf("missing search criteria")
+ }
+
+ // Find the account using the account manager
+ account, err := mngr.FindAccount(by, value)
+ if err != nil {
+ return nil, errors.Wrap(err, "user not found")
+ }
+ return account, nil
+}
diff --git a/internal/http/services/siteacc/manager.go b/pkg/siteacc/manager.go
similarity index 77%
rename from internal/http/services/siteacc/manager.go
rename to pkg/siteacc/manager.go
index 23916bd117..3774d2b9f8 100644
--- a/internal/http/services/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -26,15 +26,15 @@ import (
"sync"
"time"
+ config2 "github.com/cs3org/reva/pkg/siteacc/config"
+ data2 "github.com/cs3org/reva/pkg/siteacc/data"
+ email2 "github.com/cs3org/reva/pkg/siteacc/email"
+ panel2 "github.com/cs3org/reva/pkg/siteacc/panel"
+ registration2 "github.com/cs3org/reva/pkg/siteacc/registration"
+ sitereg2 "github.com/cs3org/reva/pkg/siteacc/sitereg"
"github.com/pkg/errors"
"github.com/rs/zerolog"
- "github.com/cs3org/reva/internal/http/services/siteacc/config"
- "github.com/cs3org/reva/internal/http/services/siteacc/data"
- "github.com/cs3org/reva/internal/http/services/siteacc/email"
- "github.com/cs3org/reva/internal/http/services/siteacc/panel"
- "github.com/cs3org/reva/internal/http/services/siteacc/registration"
- "github.com/cs3org/reva/internal/http/services/siteacc/sitereg"
"github.com/cs3org/reva/pkg/mentix/key"
"github.com/cs3org/reva/pkg/smtpclient"
)
@@ -50,20 +50,20 @@ const (
// Manager is responsible for all site account related tasks.
type Manager struct {
- conf *config.Configuration
+ conf *config2.Configuration
log *zerolog.Logger
- accounts data.Accounts
- storage data.Storage
+ accounts data2.Accounts
+ storage data2.Storage
- panel *panel.Panel
- registrationForm *registration.Form
+ panel *panel2.Panel
+ registrationForm *registration2.Form
smtp *smtpclient.SMTPCredentials
mutex sync.RWMutex
}
-func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (mngr *Manager) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -74,7 +74,7 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
}
mngr.log = log
- mngr.accounts = make(data.Accounts, 0, 32) // Reserve some space for accounts
+ mngr.accounts = make(data2.Accounts, 0, 32) // Reserve some space for accounts
// Create the site accounts storage and read all stored data
if storage, err := mngr.createStorage(conf.Storage.Driver); err == nil {
@@ -85,14 +85,14 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// Create the web interface panel
- if pnl, err := panel.NewPanel(conf, log); err == nil {
+ if pnl, err := panel2.NewPanel(conf, log); err == nil {
mngr.panel = pnl
} else {
return errors.Wrap(err, "unable to create panel")
}
// Create the web interface registrationForm
- if frm, err := registration.NewForm(conf, log); err == nil {
+ if frm, err := registration2.NewForm(conf, log); err == nil {
mngr.registrationForm = frm
} else {
return errors.Wrap(err, "unable to create registrationForm")
@@ -106,9 +106,9 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return nil
}
-func (mngr *Manager) createStorage(driver string) (data.Storage, error) {
+func (mngr *Manager) createStorage(driver string) (data2.Storage, error) {
if driver == "file" {
- return data.NewFileStorage(mngr.conf, mngr.log)
+ return data2.NewFileStorage(mngr.conf, mngr.log)
}
return nil, errors.Errorf("unknown storage driver %v", driver)
@@ -130,21 +130,21 @@ func (mngr *Manager) writeAllAccounts() {
}
}
-func (mngr *Manager) findAccount(by string, value string) (*data.Account, error) {
+func (mngr *Manager) findAccount(by string, value string) (*data2.Account, error) {
if len(value) == 0 {
return nil, errors.Errorf("no search value specified")
}
- var account *data.Account
+ var account *data2.Account
switch strings.ToLower(by) {
case FindByEmail:
- account = mngr.findAccountByPredicate(func(account *data.Account) bool { return strings.EqualFold(account.Email, value) })
+ account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return strings.EqualFold(account.Email, value) })
case FindByAPIKey:
- account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.Data.APIKey == value })
+ account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return account.Data.APIKey == value })
case FindBySiteID:
- account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.GetSiteID() == value })
+ account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return account.GetSiteID() == value })
default:
return nil, errors.Errorf("invalid search type %v", by)
@@ -157,7 +157,7 @@ func (mngr *Manager) findAccount(by string, value string) (*data.Account, error)
return nil, errors.Errorf("no user found matching the specified criteria")
}
-func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool) *data.Account {
+func (mngr *Manager) findAccountByPredicate(predicate func(*data2.Account) bool) *data2.Account {
for _, account := range mngr.accounts {
if predicate(account) {
return account
@@ -179,7 +179,7 @@ func (mngr *Manager) ShowRegistrationForm(w http.ResponseWriter) error {
}
// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
-func (mngr *Manager) CreateAccount(accountData *data.Account) error {
+func (mngr *Manager) CreateAccount(accountData *data2.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -188,12 +188,12 @@ func (mngr *Manager) CreateAccount(accountData *data.Account) error {
return errors.Errorf("an account with the specified email address already exists")
}
- if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName); err == nil {
+ if account, err := data2.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName); err == nil {
mngr.accounts = append(mngr.accounts, account)
mngr.storage.AccountAdded(account)
mngr.writeAllAccounts()
- _ = email.SendAccountCreated(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email2.SendAccountCreated(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
} else {
return errors.Wrap(err, "error while creating account")
}
@@ -202,7 +202,7 @@ func (mngr *Manager) CreateAccount(accountData *data.Account) error {
}
// UpdateAccount updates the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) error {
+func (mngr *Manager) UpdateAccount(accountData *data2.Account, copyData bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -224,7 +224,7 @@ func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) err
}
// FindAccount is used to find an account by various criteria.
-func (mngr *Manager) FindAccount(by string, value string) (*data.Account, error) {
+func (mngr *Manager) FindAccount(by string, value string) (*data2.Account, error) {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -239,7 +239,7 @@ func (mngr *Manager) FindAccount(by string, value string) (*data.Account, error)
}
// AuthorizeAccount sets the authorization status of the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AuthorizeAccount(accountData *data.Account, authorized bool) error {
+func (mngr *Manager) AuthorizeAccount(accountData *data2.Account, authorized bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -255,14 +255,14 @@ func (mngr *Manager) AuthorizeAccount(accountData *data.Account, authorized bool
mngr.writeAllAccounts()
if account.Data.Authorized && account.Data.Authorized != authorizedOld {
- _ = email.SendAccountAuthorized(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email2.SendAccountAuthorized(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
}
return nil
}
// AssignAPIKeyToAccount is used to assign a new API key to the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int) error {
+func (mngr *Manager) AssignAPIKeyToAccount(accountData *data2.Account, flags int) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -293,13 +293,13 @@ func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int)
mngr.storage.AccountUpdated(account)
mngr.writeAllAccounts()
- _ = email.SendAPIKeyAssigned(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email2.SendAPIKeyAssigned(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
return nil
}
// UnregisterAccountSite unregisters the site associated with the given account.
-func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
+func (mngr *Manager) UnregisterAccountSite(accountData *data2.Account) error {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -314,7 +314,7 @@ func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
return errors.Wrap(err, "unable to get site ID")
}
- if err := sitereg.UnregisterSite(mngr.conf.SiteRegistration.URL, account.Data.APIKey, siteID, salt); err != nil {
+ if err := sitereg2.UnregisterSite(mngr.conf.SiteRegistration.URL, account.Data.APIKey, siteID, salt); err != nil {
return errors.Wrap(err, "error while unregistering the site")
}
@@ -322,7 +322,7 @@ func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
}
// RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
+func (mngr *Manager) RemoveAccount(accountData *data2.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -339,11 +339,11 @@ func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
}
// CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
-func (mngr *Manager) CloneAccounts() data.Accounts {
+func (mngr *Manager) CloneAccounts() data2.Accounts {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
- clone := make(data.Accounts, 0)
+ clone := make(data2.Accounts, 0)
// To avoid any "deep copy" packages, use gob en- and decoding instead
var buf bytes.Buffer
@@ -353,14 +353,14 @@ func (mngr *Manager) CloneAccounts() data.Accounts {
if err := enc.Encode(mngr.accounts); err == nil {
if err := dec.Decode(&clone); err != nil {
// In case of an error, create an empty data set
- clone = make(data.Accounts, 0)
+ clone = make(data2.Accounts, 0)
}
}
return clone
}
-func newManager(conf *config.Configuration, log *zerolog.Logger) (*Manager, error) {
+func newManager(conf *config2.Configuration, log *zerolog.Logger) (*Manager, error) {
mngr := &Manager{}
if err := mngr.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the accounts manager")
diff --git a/internal/http/services/siteacc/panel/panel.go b/pkg/siteacc/panel/panel.go
similarity index 83%
rename from internal/http/services/siteacc/panel/panel.go
rename to pkg/siteacc/panel/panel.go
index ce8d6cd916..539e54010b 100644
--- a/internal/http/services/siteacc/panel/panel.go
+++ b/pkg/siteacc/panel/panel.go
@@ -22,22 +22,21 @@ import (
"html/template"
"net/http"
+ config2 "github.com/cs3org/reva/pkg/siteacc/config"
+ data2 "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/pkg/errors"
"github.com/rs/zerolog"
-
- "github.com/cs3org/reva/internal/http/services/siteacc/config"
- "github.com/cs3org/reva/internal/http/services/siteacc/data"
)
// Panel represents the web interface panel of the accounts service.
type Panel struct {
- conf *config.Configuration
+ conf *config2.Configuration
log *zerolog.Logger
tpl *template.Template
}
-func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (panel *Panel) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -58,9 +57,9 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// Execute generates the HTTP output of the panel and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
+func (panel *Panel) Execute(w http.ResponseWriter, accounts *data2.Accounts) error {
type TemplateData struct {
- Accounts *data.Accounts
+ Accounts *data2.Accounts
}
tplData := TemplateData{
@@ -71,7 +70,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) erro
}
// NewPanel creates a new web interface panel.
-func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
+func NewPanel(conf *config2.Configuration, log *zerolog.Logger) (*Panel, error) {
panel := &Panel{}
if err := panel.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the panel")
diff --git a/internal/http/services/siteacc/panel/template.go b/pkg/siteacc/panel/template.go
similarity index 100%
rename from internal/http/services/siteacc/panel/template.go
rename to pkg/siteacc/panel/template.go
diff --git a/internal/http/services/siteacc/registration/form.go b/pkg/siteacc/registration/form.go
similarity index 87%
rename from internal/http/services/siteacc/registration/form.go
rename to pkg/siteacc/registration/form.go
index e7649e891d..e6447b90f4 100644
--- a/internal/http/services/siteacc/registration/form.go
+++ b/pkg/siteacc/registration/form.go
@@ -22,21 +22,20 @@ import (
"html/template"
"net/http"
+ config2 "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/pkg/errors"
"github.com/rs/zerolog"
-
- "github.com/cs3org/reva/internal/http/services/siteacc/config"
)
// Form represents the web interface form for user account registration.
type Form struct {
- conf *config.Configuration
+ conf *config2.Configuration
log *zerolog.Logger
tpl *template.Template
}
-func (form *Form) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (form *Form) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -67,7 +66,7 @@ func (form *Form) Execute(w http.ResponseWriter) error {
}
// NewForm creates a new web interface form.
-func NewForm(conf *config.Configuration, log *zerolog.Logger) (*Form, error) {
+func NewForm(conf *config2.Configuration, log *zerolog.Logger) (*Form, error) {
form := &Form{}
if err := form.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the form")
diff --git a/internal/http/services/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
similarity index 100%
rename from internal/http/services/siteacc/registration/template.go
rename to pkg/siteacc/registration/template.go
diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go
new file mode 100644
index 0000000000..5041498358
--- /dev/null
+++ b/pkg/siteacc/siteacc.go
@@ -0,0 +1,101 @@
+// Copyright 2018-2021 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 siteacc
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog"
+)
+
+// SiteAccounts represents the main Site Accounts service object.
+type SiteAccounts struct {
+ conf *config.Configuration
+ log *zerolog.Logger
+
+ manager *Manager
+}
+
+func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+ if conf == nil {
+ return fmt.Errorf("no configuration provided")
+ }
+ siteacc.conf = conf
+
+ if log == nil {
+ return fmt.Errorf("no logger provided")
+ }
+ siteacc.log = log
+
+ // Create the accounts manager instance
+ mngr, err := newManager(conf, log)
+ if err != nil {
+ return errors.Wrap(err, "error creating the site accounts manager")
+ }
+ siteacc.manager = mngr
+
+ return nil
+}
+
+// RequestHandler returns the HTTP request handler of the service.
+func (siteacc *SiteAccounts) RequestHandler() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ defer r.Body.Close()
+
+ epHandled := false
+ for _, ep := range getEndpoints(siteacc.conf.EnableRegistrationForm) {
+ if ep.Path == r.URL.Path {
+ ep.Handler(siteacc.manager, ep, w, r)
+ epHandled = true
+ break
+ }
+ }
+
+ if !epHandled {
+ w.WriteHeader(http.StatusBadRequest)
+ _, _ = w.Write([]byte(fmt.Sprintf("Unknown endpoint %v", r.URL.Path)))
+ }
+ })
+}
+
+func (siteacc *SiteAccounts) GetPublicEndpoints() []string {
+ // TODO: REMOVE!
+ return []string{"/"}
+
+ endpoints := make([]string, 0, 5)
+ for _, ep := range getEndpoints(siteacc.conf.EnableRegistrationForm) {
+ if ep.IsPublic {
+ endpoints = append(endpoints, ep.Path)
+ }
+ }
+ return endpoints
+}
+
+// New returns a new Site Accounts service instance.
+func New(conf *config.Configuration, log *zerolog.Logger) (*SiteAccounts, error) {
+ // Configure the accounts service
+ siteacc := new(SiteAccounts)
+ if err := siteacc.initialize(conf, log); err != nil {
+ return nil, fmt.Errorf("unable to initialize SiteAccounts: %v", err)
+ }
+ return siteacc, nil
+}
diff --git a/internal/http/services/siteacc/sitereg/sitereg.go b/pkg/siteacc/sitereg/sitereg.go
similarity index 100%
rename from internal/http/services/siteacc/sitereg/sitereg.go
rename to pkg/siteacc/sitereg/sitereg.go
From 68639b0db21be505560fe3c711260425d0e03fcf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 30 Jun 2021 16:05:14 +0200
Subject: [PATCH 03/60] Add some more fields to user accounts
---
pkg/siteacc/data/account.go | 28 ++++++++--
pkg/siteacc/data/filestorage.go | 8 +--
pkg/siteacc/email/email.go | 8 +--
pkg/siteacc/manager.go | 78 ++++++++++++++--------------
pkg/siteacc/panel/panel.go | 14 ++---
pkg/siteacc/panel/template.go | 13 ++++-
pkg/siteacc/registration/form.go | 8 +--
pkg/siteacc/registration/template.go | 22 ++++++--
8 files changed, 112 insertions(+), 67 deletions(-)
diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go
index 914d5b7e12..8f24fc06ea 100644
--- a/pkg/siteacc/data/account.go
+++ b/pkg/siteacc/data/account.go
@@ -29,9 +29,19 @@ import (
// Account represents a single site account.
type Account struct {
- Email string `json:"email"`
- FirstName string `json:"firstName"`
- LastName string `json:"lastName"`
+ Email string `json:"email"`
+ FirstName string `json:"firstName"`
+ LastName string `json:"lastName"`
+ Organization string `json:"organization"`
+ Website string `json:"website"`
+ PhoneNumber string `json:"phoneNumber"`
+
+ /*
+ Password struct {
+ Hash string `json:"hash"`
+ Salt string `json:"salt"`
+ } `json:"password"`
+ */
DateCreated time.Time `json:"dateCreated"`
DateModified time.Time `json:"dateModified"`
@@ -66,6 +76,9 @@ func (acc *Account) Copy(other *Account, copyData bool) error {
// Manually update fields
acc.FirstName = other.FirstName
acc.LastName = other.LastName
+ acc.Organization = other.Organization
+ acc.Website = other.Website
+ acc.PhoneNumber = other.PhoneNumber
if copyData {
acc.Data = other.Data
@@ -85,17 +98,24 @@ func (acc *Account) verify() error {
return errors.Errorf("no or incomplete name provided")
}
+ if acc.Organization == "" {
+ return errors.Errorf("no organization provided")
+ }
+
return nil
}
// NewAccount creates a new site account.
-func NewAccount(email string, firstName, lastName string) (*Account, error) {
+func NewAccount(email string, firstName, lastName string, organization, website string, phoneNumber string) (*Account, error) {
t := time.Now()
acc := &Account{
Email: email,
FirstName: firstName,
LastName: lastName,
+ Organization: organization,
+ Website: website,
+ PhoneNumber: phoneNumber,
DateCreated: t,
DateModified: t,
Data: AccountData{
diff --git a/pkg/siteacc/data/filestorage.go b/pkg/siteacc/data/filestorage.go
index dbc788868c..e7413ac5f6 100644
--- a/pkg/siteacc/data/filestorage.go
+++ b/pkg/siteacc/data/filestorage.go
@@ -24,7 +24,7 @@ import (
"os"
"path/filepath"
- config2 "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
@@ -33,13 +33,13 @@ import (
type FileStorage struct {
Storage
- conf *config2.Configuration
+ conf *config.Configuration
log *zerolog.Logger
filePath string
}
-func (storage *FileStorage) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
+func (storage *FileStorage) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -106,7 +106,7 @@ func (storage *FileStorage) AccountRemoved(account *Account) {
}
// NewFileStorage creates a new filePath storage.
-func NewFileStorage(conf *config2.Configuration, log *zerolog.Logger) (*FileStorage, error) {
+func NewFileStorage(conf *config.Configuration, log *zerolog.Logger) (*FileStorage, error) {
storage := &FileStorage{}
if err := storage.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the filePath storage")
diff --git a/pkg/siteacc/email/email.go b/pkg/siteacc/email/email.go
index 1756cdf1b5..1e90b72068 100644
--- a/pkg/siteacc/email/email.go
+++ b/pkg/siteacc/email/email.go
@@ -22,24 +22,24 @@ import (
"bytes"
"text/template"
- data2 "github.com/cs3org/reva/pkg/siteacc/data"
+ "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/pkg/errors"
"github.com/cs3org/reva/pkg/smtpclient"
)
// SendAccountCreated sends an email about account creation.
-func SendAccountCreated(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAccountCreated(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, account, smtp)
}
// SendAPIKeyAssigned sends an email about API key assignment.
-func SendAPIKeyAssigned(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAPIKeyAssigned(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, account, smtp)
}
// SendAccountAuthorized sends an email about account authorization.
-func SendAccountAuthorized(account *data2.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
+func SendAccountAuthorized(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, account, smtp)
}
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index 3774d2b9f8..91bbf92b1f 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -26,12 +26,12 @@ import (
"sync"
"time"
- config2 "github.com/cs3org/reva/pkg/siteacc/config"
- data2 "github.com/cs3org/reva/pkg/siteacc/data"
- email2 "github.com/cs3org/reva/pkg/siteacc/email"
- panel2 "github.com/cs3org/reva/pkg/siteacc/panel"
- registration2 "github.com/cs3org/reva/pkg/siteacc/registration"
- sitereg2 "github.com/cs3org/reva/pkg/siteacc/sitereg"
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/data"
+ "github.com/cs3org/reva/pkg/siteacc/email"
+ "github.com/cs3org/reva/pkg/siteacc/panel"
+ "github.com/cs3org/reva/pkg/siteacc/registration"
+ "github.com/cs3org/reva/pkg/siteacc/sitereg"
"github.com/pkg/errors"
"github.com/rs/zerolog"
@@ -50,20 +50,20 @@ const (
// Manager is responsible for all site account related tasks.
type Manager struct {
- conf *config2.Configuration
+ conf *config.Configuration
log *zerolog.Logger
- accounts data2.Accounts
- storage data2.Storage
+ accounts data.Accounts
+ storage data.Storage
- panel *panel2.Panel
- registrationForm *registration2.Form
+ panel *panel.Panel
+ registrationForm *registration.Form
smtp *smtpclient.SMTPCredentials
mutex sync.RWMutex
}
-func (mngr *Manager) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
+func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -74,7 +74,7 @@ func (mngr *Manager) initialize(conf *config2.Configuration, log *zerolog.Logger
}
mngr.log = log
- mngr.accounts = make(data2.Accounts, 0, 32) // Reserve some space for accounts
+ mngr.accounts = make(data.Accounts, 0, 32) // Reserve some space for accounts
// Create the site accounts storage and read all stored data
if storage, err := mngr.createStorage(conf.Storage.Driver); err == nil {
@@ -85,14 +85,14 @@ func (mngr *Manager) initialize(conf *config2.Configuration, log *zerolog.Logger
}
// Create the web interface panel
- if pnl, err := panel2.NewPanel(conf, log); err == nil {
+ if pnl, err := panel.NewPanel(conf, log); err == nil {
mngr.panel = pnl
} else {
return errors.Wrap(err, "unable to create panel")
}
// Create the web interface registrationForm
- if frm, err := registration2.NewForm(conf, log); err == nil {
+ if frm, err := registration.NewForm(conf, log); err == nil {
mngr.registrationForm = frm
} else {
return errors.Wrap(err, "unable to create registrationForm")
@@ -106,9 +106,9 @@ func (mngr *Manager) initialize(conf *config2.Configuration, log *zerolog.Logger
return nil
}
-func (mngr *Manager) createStorage(driver string) (data2.Storage, error) {
+func (mngr *Manager) createStorage(driver string) (data.Storage, error) {
if driver == "file" {
- return data2.NewFileStorage(mngr.conf, mngr.log)
+ return data.NewFileStorage(mngr.conf, mngr.log)
}
return nil, errors.Errorf("unknown storage driver %v", driver)
@@ -130,21 +130,21 @@ func (mngr *Manager) writeAllAccounts() {
}
}
-func (mngr *Manager) findAccount(by string, value string) (*data2.Account, error) {
+func (mngr *Manager) findAccount(by string, value string) (*data.Account, error) {
if len(value) == 0 {
return nil, errors.Errorf("no search value specified")
}
- var account *data2.Account
+ var account *data.Account
switch strings.ToLower(by) {
case FindByEmail:
- account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return strings.EqualFold(account.Email, value) })
+ account = mngr.findAccountByPredicate(func(account *data.Account) bool { return strings.EqualFold(account.Email, value) })
case FindByAPIKey:
- account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return account.Data.APIKey == value })
+ account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.Data.APIKey == value })
case FindBySiteID:
- account = mngr.findAccountByPredicate(func(account *data2.Account) bool { return account.GetSiteID() == value })
+ account = mngr.findAccountByPredicate(func(account *data.Account) bool { return account.GetSiteID() == value })
default:
return nil, errors.Errorf("invalid search type %v", by)
@@ -157,7 +157,7 @@ func (mngr *Manager) findAccount(by string, value string) (*data2.Account, error
return nil, errors.Errorf("no user found matching the specified criteria")
}
-func (mngr *Manager) findAccountByPredicate(predicate func(*data2.Account) bool) *data2.Account {
+func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool) *data.Account {
for _, account := range mngr.accounts {
if predicate(account) {
return account
@@ -179,7 +179,7 @@ func (mngr *Manager) ShowRegistrationForm(w http.ResponseWriter) error {
}
// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
-func (mngr *Manager) CreateAccount(accountData *data2.Account) error {
+func (mngr *Manager) CreateAccount(accountData *data.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -188,12 +188,12 @@ func (mngr *Manager) CreateAccount(accountData *data2.Account) error {
return errors.Errorf("an account with the specified email address already exists")
}
- if account, err := data2.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName); err == nil {
+ if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName, accountData.Organization, accountData.Website, accountData.PhoneNumber); err == nil {
mngr.accounts = append(mngr.accounts, account)
mngr.storage.AccountAdded(account)
mngr.writeAllAccounts()
- _ = email2.SendAccountCreated(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email.SendAccountCreated(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
} else {
return errors.Wrap(err, "error while creating account")
}
@@ -202,7 +202,7 @@ func (mngr *Manager) CreateAccount(accountData *data2.Account) error {
}
// UpdateAccount updates the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) UpdateAccount(accountData *data2.Account, copyData bool) error {
+func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -224,7 +224,7 @@ func (mngr *Manager) UpdateAccount(accountData *data2.Account, copyData bool) er
}
// FindAccount is used to find an account by various criteria.
-func (mngr *Manager) FindAccount(by string, value string) (*data2.Account, error) {
+func (mngr *Manager) FindAccount(by string, value string) (*data.Account, error) {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -239,7 +239,7 @@ func (mngr *Manager) FindAccount(by string, value string) (*data2.Account, error
}
// AuthorizeAccount sets the authorization status of the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AuthorizeAccount(accountData *data2.Account, authorized bool) error {
+func (mngr *Manager) AuthorizeAccount(accountData *data.Account, authorized bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -255,14 +255,14 @@ func (mngr *Manager) AuthorizeAccount(accountData *data2.Account, authorized boo
mngr.writeAllAccounts()
if account.Data.Authorized && account.Data.Authorized != authorizedOld {
- _ = email2.SendAccountAuthorized(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email.SendAccountAuthorized(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
}
return nil
}
// AssignAPIKeyToAccount is used to assign a new API key to the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AssignAPIKeyToAccount(accountData *data2.Account, flags int) error {
+func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -293,13 +293,13 @@ func (mngr *Manager) AssignAPIKeyToAccount(accountData *data2.Account, flags int
mngr.storage.AccountUpdated(account)
mngr.writeAllAccounts()
- _ = email2.SendAPIKeyAssigned(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = email.SendAPIKeyAssigned(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
return nil
}
// UnregisterAccountSite unregisters the site associated with the given account.
-func (mngr *Manager) UnregisterAccountSite(accountData *data2.Account) error {
+func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -314,7 +314,7 @@ func (mngr *Manager) UnregisterAccountSite(accountData *data2.Account) error {
return errors.Wrap(err, "unable to get site ID")
}
- if err := sitereg2.UnregisterSite(mngr.conf.SiteRegistration.URL, account.Data.APIKey, siteID, salt); err != nil {
+ if err := sitereg.UnregisterSite(mngr.conf.SiteRegistration.URL, account.Data.APIKey, siteID, salt); err != nil {
return errors.Wrap(err, "error while unregistering the site")
}
@@ -322,7 +322,7 @@ func (mngr *Manager) UnregisterAccountSite(accountData *data2.Account) error {
}
// RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) RemoveAccount(accountData *data2.Account) error {
+func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -339,11 +339,11 @@ func (mngr *Manager) RemoveAccount(accountData *data2.Account) error {
}
// CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
-func (mngr *Manager) CloneAccounts() data2.Accounts {
+func (mngr *Manager) CloneAccounts() data.Accounts {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
- clone := make(data2.Accounts, 0)
+ clone := make(data.Accounts, 0)
// To avoid any "deep copy" packages, use gob en- and decoding instead
var buf bytes.Buffer
@@ -353,14 +353,14 @@ func (mngr *Manager) CloneAccounts() data2.Accounts {
if err := enc.Encode(mngr.accounts); err == nil {
if err := dec.Decode(&clone); err != nil {
// In case of an error, create an empty data set
- clone = make(data2.Accounts, 0)
+ clone = make(data.Accounts, 0)
}
}
return clone
}
-func newManager(conf *config2.Configuration, log *zerolog.Logger) (*Manager, error) {
+func newManager(conf *config.Configuration, log *zerolog.Logger) (*Manager, error) {
mngr := &Manager{}
if err := mngr.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the accounts manager")
diff --git a/pkg/siteacc/panel/panel.go b/pkg/siteacc/panel/panel.go
index 539e54010b..a7d1b3424a 100644
--- a/pkg/siteacc/panel/panel.go
+++ b/pkg/siteacc/panel/panel.go
@@ -22,21 +22,21 @@ import (
"html/template"
"net/http"
- config2 "github.com/cs3org/reva/pkg/siteacc/config"
- data2 "github.com/cs3org/reva/pkg/siteacc/data"
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
// Panel represents the web interface panel of the accounts service.
type Panel struct {
- conf *config2.Configuration
+ conf *config.Configuration
log *zerolog.Logger
tpl *template.Template
}
-func (panel *Panel) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
+func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -57,9 +57,9 @@ func (panel *Panel) initialize(conf *config2.Configuration, log *zerolog.Logger)
}
// Execute generates the HTTP output of the panel and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter, accounts *data2.Accounts) error {
+func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
type TemplateData struct {
- Accounts *data2.Accounts
+ Accounts *data.Accounts
}
tplData := TemplateData{
@@ -70,7 +70,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, accounts *data2.Accounts) err
}
// NewPanel creates a new web interface panel.
-func NewPanel(conf *config2.Configuration, log *zerolog.Logger) (*Panel, error) {
+func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
panel := &Panel{}
if err := panel.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the panel")
diff --git a/pkg/siteacc/panel/template.go b/pkg/siteacc/panel/template.go
index df3fa7636f..b52b0c4860 100644
--- a/pkg/siteacc/panel/template.go
+++ b/pkg/siteacc/panel/template.go
@@ -63,8 +63,17 @@ const panelTemplate = `
{{range .Accounts}}
- {{.Email}}
- {{.FirstName}} {{.LastName}} (Joined: {{.DateCreated.Format "Jan 02, 2006 15:04"}}; Last modified: {{.DateModified.Format "Jan 02, 2006 15:04"}})
+
+ {{.Email}}
+ {{.FirstName}} {{.LastName}} (Joined: {{.DateCreated.Format "Jan 02, 2006 15:04"}}; Last modified: {{.DateModified.Format "Jan 02, 2006 15:04"}})
+
+
+
+ Organization: {{.Organization}}
+ Website: {{.Website}}
+ Phone: {{.PhoneNumber}}
+
+
API Key:
diff --git a/pkg/siteacc/registration/form.go b/pkg/siteacc/registration/form.go
index e6447b90f4..e47cd4e9c2 100644
--- a/pkg/siteacc/registration/form.go
+++ b/pkg/siteacc/registration/form.go
@@ -22,20 +22,20 @@ import (
"html/template"
"net/http"
- config2 "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
// Form represents the web interface form for user account registration.
type Form struct {
- conf *config2.Configuration
+ conf *config.Configuration
log *zerolog.Logger
tpl *template.Template
}
-func (form *Form) initialize(conf *config2.Configuration, log *zerolog.Logger) error {
+func (form *Form) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -66,7 +66,7 @@ func (form *Form) Execute(w http.ResponseWriter) error {
}
// NewForm creates a new web interface form.
-func NewForm(conf *config2.Configuration, log *zerolog.Logger) (*Form, error) {
+func NewForm(conf *config.Configuration, log *zerolog.Logger) (*Form, error) {
form := &Form{}
if err := form.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the form")
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
index 8da584177d..5b52172d70 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/registration/template.go
@@ -71,6 +71,11 @@ const formTemplate = `
return false;
}
+ if (formData.get("organization") == "") {
+ setError("Please specify your organization/company.", "organization");
+ return false;
+ }
+
return true;
}
@@ -105,7 +110,10 @@ const formTemplate = `
var postData = {
"email": formData.get("email"),
"firstName": formData.get("fname"),
- "lastName": formData.get("lname")
+ "lastName": formData.get("lname"),
+ "organization": formData.get("organization"),
+ "website": formData.get("website"),
+ "phoneNumber": formData.get("phone"),
};
xhr.send(JSON.stringify(postData));
@@ -193,10 +201,18 @@ const formTemplate = `
Last name: *
-
+
Organization/Company: *
+
+
Website:
+
+
+
Phone number:
+
+
+
Fields marked with * are mandatory.
-
+
Reset
Register
From a9f422bf2727142acfcb828e2cb8e11ce51999bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Thu, 1 Jul 2021 13:56:41 +0200
Subject: [PATCH 04/60] Add user passwords
---
pkg/siteacc/data/account.go | 64 +++++++++++++++----
pkg/siteacc/endpoints.go | 36 +++++------
pkg/siteacc/manager.go | 27 +++-----
pkg/siteacc/password/password.go | 95 ++++++++++++++++++++++++++++
pkg/siteacc/registration/template.go | 39 +++++++++++-
5 files changed, 208 insertions(+), 53 deletions(-)
create mode 100644 pkg/siteacc/password/password.go
diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go
index 8f24fc06ea..5660a9cb6d 100644
--- a/pkg/siteacc/data/account.go
+++ b/pkg/siteacc/data/account.go
@@ -19,8 +19,11 @@
package data
import (
+ "bytes"
+ "encoding/gob"
"time"
+ "github.com/cs3org/reva/pkg/siteacc/password"
"github.com/pkg/errors"
"github.com/cs3org/reva/pkg/mentix/key"
@@ -36,12 +39,7 @@ type Account struct {
Website string `json:"website"`
PhoneNumber string `json:"phoneNumber"`
- /*
- Password struct {
- Hash string `json:"hash"`
- Salt string `json:"salt"`
- } `json:"password"`
- */
+ Password password.Password `json:"password"`
DateCreated time.Time `json:"dateCreated"`
DateModified time.Time `json:"dateModified"`
@@ -67,9 +65,9 @@ func (acc *Account) GetSiteID() key.SiteIdentifier {
return ""
}
-// Copy copies the data of the given account to this account; if copyData is true, the account data is copied as well.
-func (acc *Account) Copy(other *Account, copyData bool) error {
- if err := other.verify(); err != nil {
+// Update copies the data of the given account to this account; if copyData is true, the account data is copied as well.
+func (acc *Account) Update(other *Account, copyData bool) error {
+ if err := other.verify(false); err != nil {
return errors.Wrap(err, "unable to update account data")
}
@@ -87,26 +85,59 @@ func (acc *Account) Copy(other *Account, copyData bool) error {
return nil
}
-func (acc *Account) verify() error {
+// UpdatePassword assigns a new password to the account, salting and hashing it first.
+func (acc *Account) UpdatePassword(newPwd string) error {
+ pwd, err := password.GeneratePassword(newPwd)
+ if err != nil {
+ return errors.Wrap(err, "unable to update the user password")
+ }
+ acc.Password = *pwd
+ return nil
+}
+
+func (acc *Account) Clone(erasePassword bool) *Account {
+ clone := &Account{}
+
+ // To avoid any "deep copy" packages, use gob en- and decoding instead
+ var buf bytes.Buffer
+ enc := gob.NewEncoder(&buf)
+ dec := gob.NewDecoder(&buf)
+
+ if err := enc.Encode(acc); err == nil {
+ _ = dec.Decode(clone)
+ }
+
+ if erasePassword {
+ clone.Password.Clear()
+ }
+
+ return clone
+}
+
+func (acc *Account) verify(verifyPassword bool) error {
if acc.Email == "" {
return errors.Errorf("no email address provided")
} else if !utils.IsEmailValid(acc.Email) {
return errors.Errorf("invalid email address: %v", acc.Email)
}
-
if acc.FirstName == "" || acc.LastName == "" {
return errors.Errorf("no or incomplete name provided")
}
-
if acc.Organization == "" {
return errors.Errorf("no organization provided")
}
+ if verifyPassword {
+ if !acc.Password.IsValid() {
+ return errors.Errorf("no valid password set")
+ }
+ }
+
return nil
}
// NewAccount creates a new site account.
-func NewAccount(email string, firstName, lastName string, organization, website string, phoneNumber string) (*Account, error) {
+func NewAccount(email string, firstName, lastName string, organization, website string, phoneNumber string, password string) (*Account, error) {
t := time.Now()
acc := &Account{
@@ -124,7 +155,12 @@ func NewAccount(email string, firstName, lastName string, organization, website
},
}
- if err := acc.verify(); err != nil {
+ // Set the user password, which also makes sure that the given password is strong enough
+ if err := acc.UpdatePassword(password); err != nil {
+ return nil, err
+ }
+
+ if err := acc.verify(true); err != nil {
return nil, err
}
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 0979be02d5..fbc01549e6 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -58,43 +58,43 @@ func createMethodCallbacks(cbGet methodCallback, cbPost methodCallback) map[stri
func getEndpoints(enableRegistrationForm bool) []endpoint {
endpoints := []endpoint{
// Form endpoints
- {config.EndpointPanel, handlePanelEndpoint, nil, false},
+ {config.EndpointPanel, callPanelEndpoint, nil, false},
// Request endpoints
- {config.EndpointGenerateAPIKey, handleMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
- {config.EndpointVerifyAPIKey, handleMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
- {config.EndpointAssignAPIKey, handleMethodEndpoint, createMethodCallbacks(nil, handleAssignAPIKey), false},
- {config.EndpointList, handleMethodEndpoint, createMethodCallbacks(handleList, nil), false},
- {config.EndpointFind, handleMethodEndpoint, createMethodCallbacks(handleFind, nil), false},
- {config.EndpointCreate, handleMethodEndpoint, createMethodCallbacks(nil, handleCreate), true},
- {config.EndpointUpdate, handleMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false},
- {config.EndpointRemove, handleMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
- {config.EndpointAuthorize, handleMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
- {config.EndpointIsAuthorized, handleMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
- {config.EndpointUnregisterSite, handleMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false},
+ {config.EndpointGenerateAPIKey, callMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
+ {config.EndpointVerifyAPIKey, callMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
+ {config.EndpointAssignAPIKey, callMethodEndpoint, createMethodCallbacks(nil, handleAssignAPIKey), false},
+ {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.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
+ {config.EndpointAuthorize, callMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
+ {config.EndpointIsAuthorized, callMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
+ {config.EndpointUnregisterSite, callMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false},
}
if enableRegistrationForm {
- endpoints = append(endpoints, endpoint{config.EndpointRegistration, handleRegistrationEndpoint, nil, true})
+ endpoints = append(endpoints, endpoint{config.EndpointRegistration, callRegistrationEndpoint, nil, true})
}
return endpoints
}
-func handlePanelEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+func callPanelEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
if err := mngr.ShowPanel(w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface panel: %v", err)))
}
}
-func handleRegistrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+func callRegistrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
if err := mngr.ShowRegistrationForm(w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface registration registrationForm: %v", err)))
}
}
-func handleMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+func callMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
// Every request to the accounts service results in a standardized JSON response
type Response struct {
Success bool `json:"success"`
@@ -195,7 +195,7 @@ func handleAssignAPIKey(mngr *Manager, values url.Values, body []byte) (interfac
}
func handleList(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
- return mngr.CloneAccounts(), nil
+ return mngr.CloneAccounts(true), nil
}
func handleFind(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
@@ -203,7 +203,7 @@ func handleFind(mngr *Manager, values url.Values, body []byte) (interface{}, err
if err != nil {
return nil, err
}
- return map[string]interface{}{"account": account}, nil
+ return map[string]interface{}{"account": account.Clone(true)}, nil
}
func handleCreate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index 91bbf92b1f..ab23a702be 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -19,8 +19,6 @@
package siteacc
import (
- "bytes"
- "encoding/gob"
"net/http"
"strings"
"sync"
@@ -169,7 +167,7 @@ func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool)
// ShowPanel writes the panel HTTP output directly to the response writer.
func (mngr *Manager) ShowPanel(w http.ResponseWriter) error {
// The panel only shows the stored accounts and offers actions through links, so let it use cloned data
- accounts := mngr.CloneAccounts()
+ accounts := mngr.CloneAccounts(true)
return mngr.panel.Execute(w, &accounts)
}
@@ -188,7 +186,7 @@ func (mngr *Manager) CreateAccount(accountData *data.Account) error {
return errors.Errorf("an account with the specified email address already exists")
}
- if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName, accountData.Organization, accountData.Website, accountData.PhoneNumber); err == nil {
+ if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName, accountData.Organization, accountData.Website, accountData.PhoneNumber, accountData.Password.Value); err == nil {
mngr.accounts = append(mngr.accounts, account)
mngr.storage.AccountAdded(account)
mngr.writeAllAccounts()
@@ -211,7 +209,7 @@ func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) err
return errors.Wrap(err, "user to update not found")
}
- if err := account.Copy(accountData, copyData); err == nil {
+ if err := account.Update(accountData, copyData); err == nil {
account.DateModified = time.Now()
mngr.storage.AccountUpdated(account)
@@ -339,25 +337,16 @@ func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
}
// CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
-func (mngr *Manager) CloneAccounts() data.Accounts {
+func (mngr *Manager) CloneAccounts(erasePasswords bool) data.Accounts {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
- clone := make(data.Accounts, 0)
-
- // To avoid any "deep copy" packages, use gob en- and decoding instead
- var buf bytes.Buffer
- enc := gob.NewEncoder(&buf)
- dec := gob.NewDecoder(&buf)
-
- if err := enc.Encode(mngr.accounts); err == nil {
- if err := dec.Decode(&clone); err != nil {
- // In case of an error, create an empty data set
- clone = make(data.Accounts, 0)
- }
+ clones := make(data.Accounts, 0, len(mngr.accounts))
+ for _, acc := range mngr.accounts {
+ clones = append(clones, acc.Clone(erasePasswords))
}
- return clone
+ return clones
}
func newManager(conf *config.Configuration, log *zerolog.Logger) (*Manager, error) {
diff --git a/pkg/siteacc/password/password.go b/pkg/siteacc/password/password.go
new file mode 100644
index 0000000000..bfb3cc98a2
--- /dev/null
+++ b/pkg/siteacc/password/password.go
@@ -0,0 +1,95 @@
+// 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 password
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "strings"
+
+ "github.com/cs3org/reva/pkg/utils"
+ "github.com/pkg/errors"
+)
+
+// Password holds a hash password alongside its salt value.
+type Password struct {
+ Value string `json:"value"`
+ Salt string `json:"salt"`
+}
+
+const (
+ passwordMinLength = 8
+ passwordSaltLength = 16
+)
+
+// IsValid checks whether the password is valid.
+func (password *Password) IsValid() bool {
+ return len(password.Value) == 64 && len(password.Salt) == passwordSaltLength
+}
+
+// Clear resets the password.
+func (password *Password) Clear() {
+ password.Value = ""
+ password.Salt = ""
+}
+
+// Compare checks whether the given password string equals the stored one.
+func (password *Password) Compare(pwd string) bool {
+ hashedPwd := hashPassword(pwd, password.Salt)
+ return hashedPwd == password.Value
+}
+
+// GeneratePassword salts and hashes the given password.
+func GeneratePassword(pwd string) (*Password, error) {
+ if err := VerifyPassword(pwd); err != nil {
+ return nil, errors.Wrap(err, "invalid password")
+ }
+
+ // Create a random salt string
+ salt := utils.RandString(passwordSaltLength)
+
+ return &Password{Value: hashPassword(pwd, salt), Salt: salt}, nil
+}
+
+func hashPassword(pwd, salt string) string {
+ saltedPwd := pwd + salt
+
+ // Value the salted password using SHA256
+ h := sha256.New()
+ h.Write([]byte(saltedPwd))
+ return fmt.Sprintf("%x", h.Sum(nil))
+}
+
+// VerifyPassword checks whether the given password abides to the enforced password strength.
+func VerifyPassword(pwd string) error {
+ if len(pwd) < passwordMinLength {
+ return errors.Errorf("the password must be at least 8 characters long")
+ }
+ if !strings.ContainsAny(pwd, "abcdefghijklmnopqrstuvwxyz") {
+ return errors.Errorf("the password must contain at least one lowercase letter")
+ }
+ if !strings.ContainsAny(pwd, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
+ return errors.Errorf("the password must contain at least one uppercase letter")
+ }
+ if !strings.ContainsAny(pwd, "0123456789") {
+ return errors.Errorf("the password must contain at least one digit")
+ }
+
+ return nil
+}
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
index 5b52172d70..ae38cd8f53 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/registration/template.go
@@ -76,6 +76,21 @@ const formTemplate = `
return false;
}
+ if (formData.get("password") == "") {
+ setError("Please set a password.", "password");
+ return false;
+ }
+
+ if (formData.get("password2") == "") {
+ setError("Please repeat your password.", "password2");
+ return false;
+ }
+
+ if (formData.get("password") != formData.get("password2")) {
+ setError("The entered passwords do not match.", "password2");
+ return false;
+ }
+
return true;
}
@@ -114,6 +129,9 @@ const formTemplate = `
"organization": formData.get("organization"),
"website": formData.get("website"),
"phoneNumber": formData.get("phone"),
+ "password": {
+ "value": formData.get("password")
+ }
};
xhr.send(JSON.stringify(postData));
@@ -209,10 +227,27 @@ const formTemplate = `
Phone number:
-
+
+
+
Password: *
+
+
Repeat password: *
+
+
+
+ The password must fulfil the following criteria:
+
+ Must be at least 8 characters long
+ Must contain at least 1 lowercase letter
+ Must contain at least 1 uppercase letter
+ Must contain at least 1 digit
+
+
+
+
Fields marked with * are mandatory.
-
+
Reset
Register
From 42ed780592b8e3e89498fad79afd6fcc91bdc2ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Fri, 2 Jul 2021 12:52:39 +0200
Subject: [PATCH 05/60] Minor corrections
---
pkg/siteacc/registration/template.go | 6 +++---
pkg/siteacc/siteacc.go | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
index ae38cd8f53..f0fe1369c1 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/registration/template.go
@@ -222,16 +222,16 @@ const formTemplate = `
Organization/Company: *
Website:
-
+
Phone number:
-
+
Password: *
- Repeat password: *
+ Confirm password: *
diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go
index 5041498358..c4be3de04d 100644
--- a/pkg/siteacc/siteacc.go
+++ b/pkg/siteacc/siteacc.go
@@ -56,8 +56,8 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog
return nil
}
-// RequestHandler returns the HTTP request handler of the service.
-func (siteacc *SiteAccounts) RequestHandler() http.Handler {
+// HTTPHandler returns the HTTP request handler of the service.
+func (siteacc *SiteAccounts) HTTPHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
From b3fe4e1184c712c060968ed94a4f24d795d4bc10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Fri, 2 Jul 2021 13:49:17 +0200
Subject: [PATCH 06/60] Rename panel to admin
---
pkg/siteacc/{panel => admin}/panel.go | 16 ++++++++--------
pkg/siteacc/{panel => admin}/template.go | 2 +-
pkg/siteacc/config/config.go | 2 --
pkg/siteacc/config/endpoints.go | 4 ++--
pkg/siteacc/endpoints.go | 17 +++++++----------
pkg/siteacc/manager.go | 20 ++++++++++----------
pkg/siteacc/siteacc.go | 8 ++++----
7 files changed, 32 insertions(+), 37 deletions(-)
rename pkg/siteacc/{panel => admin}/panel.go (79%)
rename pkg/siteacc/{panel => admin}/template.go (99%)
diff --git a/pkg/siteacc/panel/panel.go b/pkg/siteacc/admin/panel.go
similarity index 79%
rename from pkg/siteacc/panel/panel.go
rename to pkg/siteacc/admin/panel.go
index a7d1b3424a..b89b4f7bd4 100644
--- a/pkg/siteacc/panel/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -16,7 +16,7 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
-package panel
+package admin
import (
"html/template"
@@ -28,15 +28,15 @@ import (
"github.com/rs/zerolog"
)
-// Panel represents the web interface panel of the accounts service.
-type Panel struct {
+// AdministrationPanel represents the web interface panel of the accounts service administration.
+type AdministrationPanel struct {
conf *config.Configuration
log *zerolog.Logger
tpl *template.Template
}
-func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (panel *AdministrationPanel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -57,7 +57,7 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// Execute generates the HTTP output of the panel and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
+func (panel *AdministrationPanel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
type TemplateData struct {
Accounts *data.Accounts
}
@@ -70,10 +70,10 @@ func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) erro
}
// NewPanel creates a new web interface panel.
-func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
- panel := &Panel{}
+func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*AdministrationPanel, error) {
+ panel := &AdministrationPanel{}
if err := panel.initialize(conf, log); err != nil {
- return nil, errors.Wrapf(err, "unable to initialize the panel")
+ return nil, errors.Wrapf(err, "unable to initialize the administration panel")
}
return panel, nil
}
diff --git a/pkg/siteacc/panel/template.go b/pkg/siteacc/admin/template.go
similarity index 99%
rename from pkg/siteacc/panel/template.go
rename to pkg/siteacc/admin/template.go
index b52b0c4860..23618e4bd0 100644
--- a/pkg/siteacc/panel/template.go
+++ b/pkg/siteacc/admin/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 panel
+package admin
const panelTemplate = `
diff --git a/pkg/siteacc/config/config.go b/pkg/siteacc/config/config.go
index 60eeec1902..15c23bb68f 100644
--- a/pkg/siteacc/config/config.go
+++ b/pkg/siteacc/config/config.go
@@ -32,8 +32,6 @@ type Configuration struct {
} `mapstructure:"file"`
} `mapstructure:"storage"`
- EnableRegistrationForm bool `mapstructure:"enable_registration_form"`
-
SMTP *smtpclient.SMTPCredentials `mapstructure:"smtp"`
NotificationsMail string `mapstructure:"notifications_mail"`
diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
index 8042085e61..cc4be321e2 100644
--- a/pkg/siteacc/config/endpoints.go
+++ b/pkg/siteacc/config/endpoints.go
@@ -19,8 +19,8 @@
package config
const (
- // EndpointPanel is the endpoint path of the web interface panel.
- EndpointPanel = "/panel"
+ // EndpointAdministration is the endpoint path of the web interface administration panel.
+ EndpointAdministration = "/admin"
// EndpointRegistration is the endpoint path of the web interface registration form.
EndpointRegistration = "/register"
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index fbc01549e6..4641cb4c07 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -55,10 +55,11 @@ func createMethodCallbacks(cbGet methodCallback, cbPost methodCallback) map[stri
return callbacks
}
-func getEndpoints(enableRegistrationForm bool) []endpoint {
+func getEndpoints() []endpoint {
endpoints := []endpoint{
- // Form endpoints
- {config.EndpointPanel, callPanelEndpoint, nil, false},
+ // Form/panel endpoints
+ {config.EndpointAdministration, callAdministrationEndpoint, nil, false},
+ {config.EndpointRegistration, callRegistrationEndpoint, nil, true},
// Request endpoints
{config.EndpointGenerateAPIKey, callMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
{config.EndpointVerifyAPIKey, callMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
@@ -73,17 +74,13 @@ func getEndpoints(enableRegistrationForm bool) []endpoint {
{config.EndpointUnregisterSite, callMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false},
}
- if enableRegistrationForm {
- endpoints = append(endpoints, endpoint{config.EndpointRegistration, callRegistrationEndpoint, nil, true})
- }
-
return endpoints
}
-func callPanelEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowPanel(w); err != nil {
+func callAdministrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+ if err := mngr.ShowAdministrationPanel(w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface panel: %v", err)))
+ _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface administration adminPanel: %v", err)))
}
}
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index ab23a702be..5a04fabe60 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -24,10 +24,10 @@ import (
"sync"
"time"
+ "github.com/cs3org/reva/pkg/siteacc/admin"
"github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/data"
"github.com/cs3org/reva/pkg/siteacc/email"
- "github.com/cs3org/reva/pkg/siteacc/panel"
"github.com/cs3org/reva/pkg/siteacc/registration"
"github.com/cs3org/reva/pkg/siteacc/sitereg"
"github.com/pkg/errors"
@@ -54,7 +54,7 @@ type Manager struct {
accounts data.Accounts
storage data.Storage
- panel *panel.Panel
+ adminPanel *admin.AdministrationPanel
registrationForm *registration.Form
smtp *smtpclient.SMTPCredentials
@@ -82,11 +82,11 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return errors.Wrap(err, "unable to create accounts storage")
}
- // Create the web interface panel
- if pnl, err := panel.NewPanel(conf, log); err == nil {
- mngr.panel = pnl
+ // Create the web interface adminPanel
+ if pnl, err := admin.NewPanel(conf, log); err == nil {
+ mngr.adminPanel = pnl
} else {
- return errors.Wrap(err, "unable to create panel")
+ return errors.Wrap(err, "unable to create adminPanel")
}
// Create the web interface registrationForm
@@ -164,11 +164,11 @@ func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool)
return nil
}
-// ShowPanel writes the panel HTTP output directly to the response writer.
-func (mngr *Manager) ShowPanel(w http.ResponseWriter) error {
- // The panel only shows the stored accounts and offers actions through links, so let it use cloned data
+// ShowAdministrationPanel writes the adminPanel HTTP output directly to the response writer.
+func (mngr *Manager) ShowAdministrationPanel(w http.ResponseWriter) error {
+ // The adminPanel only shows the stored accounts and offers actions through links, so let it use cloned data
accounts := mngr.CloneAccounts(true)
- return mngr.panel.Execute(w, &accounts)
+ return mngr.adminPanel.Execute(w, &accounts)
}
// ShowRegistrationForm writes the registration registrationForm HTTP output directly to the response writer.
diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go
index c4be3de04d..c0223f673e 100644
--- a/pkg/siteacc/siteacc.go
+++ b/pkg/siteacc/siteacc.go
@@ -56,13 +56,13 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog
return nil
}
-// HTTPHandler returns the HTTP request handler of the service.
-func (siteacc *SiteAccounts) HTTPHandler() http.Handler {
+// RequestHandler returns the HTTP request handler of the service.
+func (siteacc *SiteAccounts) RequestHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
epHandled := false
- for _, ep := range getEndpoints(siteacc.conf.EnableRegistrationForm) {
+ for _, ep := range getEndpoints() {
if ep.Path == r.URL.Path {
ep.Handler(siteacc.manager, ep, w, r)
epHandled = true
@@ -82,7 +82,7 @@ func (siteacc *SiteAccounts) GetPublicEndpoints() []string {
return []string{"/"}
endpoints := make([]string, 0, 5)
- for _, ep := range getEndpoints(siteacc.conf.EnableRegistrationForm) {
+ for _, ep := range getEndpoints() {
if ep.IsPublic {
endpoints = append(endpoints, ep.Path)
}
From cf8b597bfd518c709e060672e72ea3aea4ed8656 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Fri, 2 Jul 2021 15:33:04 +0200
Subject: [PATCH 07/60] Use bcrypt for storing passwords
---
pkg/siteacc/data/account.go | 9 +++---
pkg/siteacc/password/password.go | 54 +++++++++++++-------------------
2 files changed, 26 insertions(+), 37 deletions(-)
diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go
index 5660a9cb6d..55d17ae893 100644
--- a/pkg/siteacc/data/account.go
+++ b/pkg/siteacc/data/account.go
@@ -85,16 +85,15 @@ func (acc *Account) Update(other *Account, copyData bool) error {
return nil
}
-// UpdatePassword assigns a new password to the account, salting and hashing it first.
-func (acc *Account) UpdatePassword(newPwd string) error {
- pwd, err := password.GeneratePassword(newPwd)
- if err != nil {
+// UpdatePassword assigns a new password to the account, hashing it first.
+func (acc *Account) UpdatePassword(pwd string) error {
+ if err := acc.Password.Set(pwd); err != nil {
return errors.Wrap(err, "unable to update the user password")
}
- acc.Password = *pwd
return nil
}
+// Clone creates a copy of the account; if erasePassword is set to true, the password will be cleared in the cloned object.
func (acc *Account) Clone(erasePassword bool) *Account {
clone := &Account{}
diff --git a/pkg/siteacc/password/password.go b/pkg/siteacc/password/password.go
index bfb3cc98a2..049252c346 100644
--- a/pkg/siteacc/password/password.go
+++ b/pkg/siteacc/password/password.go
@@ -19,61 +19,51 @@
package password
import (
- "crypto/sha256"
"fmt"
"strings"
- "github.com/cs3org/reva/pkg/utils"
"github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
)
// Password holds a hash password alongside its salt value.
type Password struct {
Value string `json:"value"`
- Salt string `json:"salt"`
}
const (
- passwordMinLength = 8
- passwordSaltLength = 16
+ passwordMinLength = 8
)
-// IsValid checks whether the password is valid.
-func (password *Password) IsValid() bool {
- return len(password.Value) == 64 && len(password.Salt) == passwordSaltLength
-}
+// Set sets a new password by hashing the plaintext version using bcrypt.
+func (password *Password) Set(pwd string) error {
+ if err := VerifyPassword(pwd); err != nil {
+ return errors.Wrap(err, "invalid password")
+ }
-// Clear resets the password.
-func (password *Password) Clear() {
- password.Value = ""
- password.Salt = ""
+ pwdData, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
+ if err != nil {
+ return errors.Wrap(err, "unable to generate password hash")
+ }
+ password.Value = string(pwdData)
+ fmt.Println(password.Value)
+ return nil
}
// Compare checks whether the given password string equals the stored one.
func (password *Password) Compare(pwd string) bool {
- hashedPwd := hashPassword(pwd, password.Salt)
- return hashedPwd == password.Value
+ return bcrypt.CompareHashAndPassword([]byte(password.Value), []byte(pwd)) == nil
}
-// GeneratePassword salts and hashes the given password.
-func GeneratePassword(pwd string) (*Password, error) {
- if err := VerifyPassword(pwd); err != nil {
- return nil, errors.Wrap(err, "invalid password")
- }
-
- // Create a random salt string
- salt := utils.RandString(passwordSaltLength)
-
- return &Password{Value: hashPassword(pwd, salt), Salt: salt}, nil
+// IsValid checks whether the password is valid.
+func (password *Password) IsValid() bool {
+ // bcrypt hashes are in the form of $[version]$[cost]$[22 character salt][31 character hash], so they have a minimum length of 58
+ return len(password.Value) > 58 && strings.Count(password.Value, "$") >= 3
}
-func hashPassword(pwd, salt string) string {
- saltedPwd := pwd + salt
-
- // Value the salted password using SHA256
- h := sha256.New()
- h.Write([]byte(saltedPwd))
- return fmt.Sprintf("%x", h.Sum(nil))
+// Clear resets the password.
+func (password *Password) Clear() {
+ password.Value = ""
}
// VerifyPassword checks whether the given password abides to the enforced password strength.
From 54b52582bb6fb43754fd937196502a35c760fba9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Mon, 5 Jul 2021 14:41:09 +0200
Subject: [PATCH 08/60] Implement an HTML panel engine
---
pkg/siteacc/admin/panel.go | 55 ++--
pkg/siteacc/admin/template.go | 73 +++---
pkg/siteacc/html/panel.go | 97 +++++++
pkg/siteacc/html/provider.go | 34 +++
pkg/siteacc/html/template.go | 157 +++++++++++
pkg/siteacc/registration/form.go | 53 ++--
pkg/siteacc/registration/template.go | 377 ++++++++++-----------------
7 files changed, 526 insertions(+), 320 deletions(-)
create mode 100644 pkg/siteacc/html/panel.go
create mode 100644 pkg/siteacc/html/provider.go
create mode 100644 pkg/siteacc/html/template.go
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index b89b4f7bd4..cf5580aa56 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -19,44 +19,59 @@
package admin
import (
- "html/template"
"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"
)
// AdministrationPanel represents the web interface panel of the accounts service administration.
type AdministrationPanel struct {
- conf *config.Configuration
- log *zerolog.Logger
+ html.ContentProvider
- tpl *template.Template
+ htmlPanel *html.Panel
}
func (panel *AdministrationPanel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
- if conf == nil {
- return errors.Errorf("no configuration provided")
+ // 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.conf = conf
+ panel.htmlPanel = htmlPanel
- if log == nil {
- return errors.Errorf("no logger provided")
- }
- panel.log = log
+ return nil
+}
- // Create the panel template
- panel.tpl = template.New("panel")
- if _, err := panel.tpl.Parse(panelTemplate); err != nil {
- return errors.Wrap(err, "error while parsing panel template")
- }
+// GetTitle returns the title of the htmlPanel.
+func (panel *AdministrationPanel) GetTitle() string {
+ return "Administration Panel"
+}
- return nil
+// GetCaption returns the caption which is displayed on the htmlPanel.
+func (panel *AdministrationPanel) GetCaption() string {
+ return "Accounts ({{.Accounts | len}})"
+}
+
+// GetContentJavaScript delivers additional JavaScript code.
+func (panel *AdministrationPanel) GetContentJavaScript() string {
+ return tplJavaScript
+}
+
+// GetContentStyleSheet delivers additional stylesheet code.
+func (panel *AdministrationPanel) GetContentStyleSheet() string {
+ return tplStyleSheet
+}
+
+// GetContentBody delivers the actual body content.
+func (panel *AdministrationPanel) GetContentBody() string {
+ return tplBody
}
-// Execute generates the HTTP output of the panel and writes it to the response writer.
+// Execute generates the HTTP output of the htmlPanel and writes it to the response writer.
func (panel *AdministrationPanel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
type TemplateData struct {
Accounts *data.Accounts
@@ -66,10 +81,10 @@ func (panel *AdministrationPanel) Execute(w http.ResponseWriter, accounts *data.
Accounts: accounts,
}
- return panel.tpl.Execute(w, tplData)
+ return panel.htmlPanel.Execute(w, tplData)
}
-// NewPanel creates a new web interface panel.
+// NewPanel creates a new administration panel.
func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*AdministrationPanel, error) {
panel := &AdministrationPanel{}
if err := panel.initialize(conf, log); err != nil {
diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go
index 23618e4bd0..479c350deb 100644
--- a/pkg/siteacc/admin/template.go
+++ b/pkg/siteacc/admin/template.go
@@ -18,47 +18,41 @@
package admin
-const panelTemplate = `
-
-
-
-
-
- Accounts panel
-
-
+ xhr.send(JSON.stringify(postData));
+}
+`
-Accounts ({{.Accounts | len}})
-
+const tplStyleSheet = `
+html * {
+ font-family: monospace !important;
+}
+`
+
+const tplBody = `
+
{{range .Accounts}}
@@ -114,8 +108,5 @@ const panelTemplate = `
{{end}}
-
-
-
-
+
`
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
new file mode 100644
index 0000000000..06f74d273a
--- /dev/null
+++ b/pkg/siteacc/html/panel.go
@@ -0,0 +1,97 @@
+// 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 html
+
+import (
+ "html/template"
+ "net/http"
+ "strings"
+
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog"
+)
+
+// Panel provides basic HTML panel functionality.
+type Panel struct {
+ conf *config.Configuration
+ log *zerolog.Logger
+
+ provider ContentProvider
+
+ tpl *template.Template
+}
+
+func (panel *Panel) initialize(name string, provider ContentProvider, conf *config.Configuration, log *zerolog.Logger) error {
+ if conf == nil {
+ return errors.Errorf("no configuration provided")
+ }
+ panel.conf = conf
+
+ if log == nil {
+ return errors.Errorf("no logger provided")
+ }
+ panel.log = log
+
+ if provider == nil {
+ return errors.Errorf("no content provider provided")
+ }
+ panel.provider = provider
+
+ // Create the panel template
+ content, err := panel.compile()
+ if err != nil {
+ return errors.Wrap(err, "error while compiling the panel template")
+ }
+
+ panel.tpl = template.New(name)
+ if _, err := panel.tpl.Parse(content); err != nil {
+ return errors.Wrap(err, "error while parsing the panel template")
+ }
+
+ return nil
+}
+
+func (panel *Panel) compile() (string, error) {
+ content := panelTemplate
+
+ // Replace placeholders by the values provided by the content provider
+ content = strings.ReplaceAll(content, "$(TITLE)", panel.provider.GetTitle())
+ content = strings.ReplaceAll(content, "$(CAPTION)", panel.provider.GetCaption())
+
+ content = strings.ReplaceAll(content, "$(CONTENT_JAVASCRIPT)", panel.provider.GetContentJavaScript())
+ content = strings.ReplaceAll(content, "$(CONTENT_STYLESHEET)", panel.provider.GetContentStyleSheet())
+ content = strings.ReplaceAll(content, "$(CONTENT_BODY)", panel.provider.GetContentBody())
+
+ return content, nil
+}
+
+// Execute generates the HTTP output of the panel and writes it to the response writer.
+func (panel *Panel) Execute(w http.ResponseWriter, data interface{}) error {
+ return panel.tpl.Execute(w, data)
+}
+
+// NewPanel creates a new panel.
+func NewPanel(name string, provider ContentProvider, conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
+ panel := &Panel{}
+ if err := panel.initialize(name, provider, conf, log); err != nil {
+ return nil, errors.Wrapf(err, "unable to initialize the panel")
+ }
+ return panel, nil
+}
diff --git a/pkg/siteacc/html/provider.go b/pkg/siteacc/html/provider.go
new file mode 100644
index 0000000000..668b920568
--- /dev/null
+++ b/pkg/siteacc/html/provider.go
@@ -0,0 +1,34 @@
+// 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 html
+
+// ContentProvider defines various methods for HTML content providers.
+type ContentProvider interface {
+ // GetTitle returns the title of the panel.
+ GetTitle() string
+ // GetCaption returns the caption which is displayed on the panel.
+ GetCaption() string
+
+ // GetContentJavaScript delivers additional JavaScript code.
+ GetContentJavaScript() string
+ // GetContentStyleSheet delivers additional stylesheet code.
+ GetContentStyleSheet() string
+ // GetContentBody delivers the actual body content.
+ GetContentBody() string
+}
diff --git a/pkg/siteacc/html/template.go b/pkg/siteacc/html/template.go
new file mode 100644
index 0000000000..c24d530054
--- /dev/null
+++ b/pkg/siteacc/html/template.go
@@ -0,0 +1,157 @@
+// 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 html
+
+const panelTemplate = `
+
+
+
+
+
+ $(TITLE)
+
+
+
+
+
$(CAPTION)
+
+ $(CONTENT_BODY)
+
+
+
+
+
+
+
+
+
+
+`
diff --git a/pkg/siteacc/registration/form.go b/pkg/siteacc/registration/form.go
index e47cd4e9c2..b849f01969 100644
--- a/pkg/siteacc/registration/form.go
+++ b/pkg/siteacc/registration/form.go
@@ -19,40 +19,55 @@
package registration
import (
- "html/template"
"net/http"
"github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/html"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
// Form represents the web interface form for user account registration.
type Form struct {
- conf *config.Configuration
- log *zerolog.Logger
+ html.ContentProvider
- tpl *template.Template
+ htmlPanel *html.Panel
}
func (form *Form) initialize(conf *config.Configuration, log *zerolog.Logger) error {
- if conf == nil {
- return errors.Errorf("no configuration provided")
+ // Create the internal HTML panel
+ htmlPanel, err := html.NewPanel("registration-form", form, conf, log)
+ if err != nil {
+ return errors.Wrap(err, "unable to create the registration form")
}
- form.conf = conf
+ form.htmlPanel = htmlPanel
- if log == nil {
- return errors.Errorf("no logger provided")
- }
- form.log = log
+ return nil
+}
- // Create the form template
- form.tpl = template.New("form")
- if _, err := form.tpl.Parse(formTemplate); err != nil {
- return errors.Wrap(err, "error while parsing form template")
- }
+// GetTitle returns the title of the htmlPanel.
+func (form *Form) GetTitle() string {
+ return "ScienceMesh Account Registration"
+}
- return nil
+// GetCaption returns the caption which is displayed on the htmlPanel.
+func (form *Form) GetCaption() string {
+ return "Welcome to the ScienceMesh Account Registration!"
+}
+
+// GetContentJavaScript delivers additional JavaScript code.
+func (form *Form) GetContentJavaScript() string {
+ return tplJavaScript
+}
+
+// GetContentStyleSheet delivers additional stylesheet code.
+func (form *Form) GetContentStyleSheet() string {
+ return tplStyleSheet
+}
+
+// GetContentBody delivers the actual body content.
+func (form *Form) GetContentBody() string {
+ return tplBody
}
// Execute generates the HTTP output of the form and writes it to the response writer.
@@ -62,14 +77,14 @@ func (form *Form) Execute(w http.ResponseWriter) error {
tplData := TemplateData{}
- return form.tpl.Execute(w, tplData)
+ return form.htmlPanel.Execute(w, tplData)
}
// NewForm creates a new web interface form.
func NewForm(conf *config.Configuration, log *zerolog.Logger) (*Form, error) {
form := &Form{}
if err := form.initialize(conf, log); err != nil {
- return nil, errors.Wrapf(err, "unable to initialize the form")
+ return nil, errors.Wrapf(err, "unable to initialize the registration form")
}
return form, nil
}
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
index f0fe1369c1..a9fd1b2862 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/registration/template.go
@@ -18,250 +18,147 @@
package registration
-const formTemplate = `
-
-
-
-
-
- ScienceMesh Account Registration
-
-
-
-
-
Welcome to the ScienceMesh Account Registration!
-
-
Fill out the form below to register for a ScienceMesh account. A confirmation email will be sent to you shortly after registration.
-
- After inspection by a ScienceMesh administrator, you will also receive an API key which can then be used in the
- ownCloud or
- Nextcloud web application.
-
-
-
-
-
- Sending registration... this should only take a moment.
-
-
- Your registration was successful! Please check your inbox for a confirmation email.
-
-
-
+const tplBody = `
+
+
Fill out the form below to register for a ScienceMesh account. A confirmation email will be sent to you shortly after registration.
+
+ After inspection by a ScienceMesh administrator, you will also receive an API key which can then be used in the
+ ownCloud or
+ Nextcloud web application.
+
+
+
+
-
-
`
From 6abccff0c9e535e5578dd08299d15839ebdad2d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Mon, 5 Jul 2021 14:47:27 +0200
Subject: [PATCH 09/60] Add user role to accounts
---
pkg/siteacc/admin/template.go | 1 +
pkg/siteacc/data/account.go | 8 +++++++-
pkg/siteacc/manager.go | 2 +-
pkg/siteacc/registration/template.go | 8 ++++++++
4 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/pkg/siteacc/admin/template.go b/pkg/siteacc/admin/template.go
index 479c350deb..a25dbadce3 100644
--- a/pkg/siteacc/admin/template.go
+++ b/pkg/siteacc/admin/template.go
@@ -65,6 +65,7 @@ const tplBody = `
Organization: {{.Organization}}
Website: {{.Website}}
+ Role: {{.Role}}
Phone: {{.PhoneNumber}}
diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go
index 55d17ae893..1d46707c36 100644
--- a/pkg/siteacc/data/account.go
+++ b/pkg/siteacc/data/account.go
@@ -37,6 +37,7 @@ type Account struct {
LastName string `json:"lastName"`
Organization string `json:"organization"`
Website string `json:"website"`
+ Role string `json:"role"`
PhoneNumber string `json:"phoneNumber"`
Password password.Password `json:"password"`
@@ -76,6 +77,7 @@ func (acc *Account) Update(other *Account, copyData bool) error {
acc.LastName = other.LastName
acc.Organization = other.Organization
acc.Website = other.Website
+ acc.Role = other.Role
acc.PhoneNumber = other.PhoneNumber
if copyData {
@@ -125,6 +127,9 @@ func (acc *Account) verify(verifyPassword bool) error {
if acc.Organization == "" {
return errors.Errorf("no organization provided")
}
+ if acc.Role == "" {
+ return errors.Errorf("no role provided")
+ }
if verifyPassword {
if !acc.Password.IsValid() {
@@ -136,7 +141,7 @@ func (acc *Account) verify(verifyPassword bool) error {
}
// NewAccount creates a new site account.
-func NewAccount(email string, firstName, lastName string, organization, website string, phoneNumber string, password string) (*Account, error) {
+func NewAccount(email string, firstName, lastName string, organization, website string, role string, phoneNumber string, password string) (*Account, error) {
t := time.Now()
acc := &Account{
@@ -145,6 +150,7 @@ func NewAccount(email string, firstName, lastName string, organization, website
LastName: lastName,
Organization: organization,
Website: website,
+ Role: role,
PhoneNumber: phoneNumber,
DateCreated: t,
DateModified: t,
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index 5a04fabe60..f810656353 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -186,7 +186,7 @@ func (mngr *Manager) CreateAccount(accountData *data.Account) error {
return errors.Errorf("an account with the specified email address already exists")
}
- if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName, accountData.Organization, accountData.Website, accountData.PhoneNumber, accountData.Password.Value); err == nil {
+ if account, err := data.NewAccount(accountData.Email, accountData.FirstName, accountData.LastName, accountData.Organization, accountData.Website, accountData.Role, accountData.PhoneNumber, accountData.Password.Value); err == nil {
mngr.accounts = append(mngr.accounts, account)
mngr.storage.AccountAdded(account)
mngr.writeAllAccounts()
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/registration/template.go
index a9fd1b2862..ffc34fc4fa 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/registration/template.go
@@ -40,6 +40,11 @@ function verifyForm(formData) {
return false;
}
+ if (formData.get("role") == "") {
+ setState(STATE_ERROR, "Please specify your role within the organization/company.", "form", "role", true);
+ return false;
+ }
+
if (formData.get("password") == "") {
setState(STATE_ERROR, "Please set a password.", "form", "password", true);
return false;
@@ -87,6 +92,7 @@ function handleAction(action) {
"lastName": formData.get("lname"),
"organization": formData.get("organization"),
"website": formData.get("website"),
+ "role": formData.get("role"),
"phoneNumber": formData.get("phone"),
"password": {
"value": formData.get("password")
@@ -132,6 +138,8 @@ const tplBody = `
Website:
+ Role: *
+
Phone number:
From e5a9f2e52222139d707d29357994bc65d11fe38b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Mon, 5 Jul 2021 16:18:07 +0200
Subject: [PATCH 10/60] Move endpoint /register to /account
---
.../form.go => account/panel.go} | 38 +++++++++----------
.../{registration => account}/template.go | 2 +-
pkg/siteacc/admin/panel.go | 24 ++++++------
pkg/siteacc/config/endpoints.go | 4 +-
pkg/siteacc/endpoints.go | 10 ++---
pkg/siteacc/manager.go | 29 +++++++-------
6 files changed, 54 insertions(+), 53 deletions(-)
rename pkg/siteacc/{registration/form.go => account/panel.go} (64%)
rename pkg/siteacc/{registration => account}/template.go (99%)
diff --git a/pkg/siteacc/registration/form.go b/pkg/siteacc/account/panel.go
similarity index 64%
rename from pkg/siteacc/registration/form.go
rename to pkg/siteacc/account/panel.go
index b849f01969..a5014af6a4 100644
--- a/pkg/siteacc/registration/form.go
+++ b/pkg/siteacc/account/panel.go
@@ -16,7 +16,7 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
-package registration
+package account
import (
"net/http"
@@ -27,64 +27,64 @@ import (
"github.com/rs/zerolog"
)
-// Form represents the web interface form for user account registration.
-type Form struct {
+// Panel represents the account panel.
+type Panel struct {
html.ContentProvider
htmlPanel *html.Panel
}
-func (form *Form) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
// Create the internal HTML panel
- htmlPanel, err := html.NewPanel("registration-form", form, conf, log)
+ htmlPanel, err := html.NewPanel("account-panel", panel, conf, log)
if err != nil {
- return errors.Wrap(err, "unable to create the registration form")
+ return errors.Wrap(err, "unable to create the account panel")
}
- form.htmlPanel = htmlPanel
+ panel.htmlPanel = htmlPanel
return nil
}
// GetTitle returns the title of the htmlPanel.
-func (form *Form) GetTitle() string {
- return "ScienceMesh Account Registration"
+func (panel *Panel) GetTitle() string {
+ return "ScienceMesh Account Panel"
}
// GetCaption returns the caption which is displayed on the htmlPanel.
-func (form *Form) GetCaption() string {
+func (panel *Panel) GetCaption() string {
return "Welcome to the ScienceMesh Account Registration!"
}
// GetContentJavaScript delivers additional JavaScript code.
-func (form *Form) GetContentJavaScript() string {
+func (panel *Panel) GetContentJavaScript() string {
return tplJavaScript
}
// GetContentStyleSheet delivers additional stylesheet code.
-func (form *Form) GetContentStyleSheet() string {
+func (panel *Panel) GetContentStyleSheet() string {
return tplStyleSheet
}
// GetContentBody delivers the actual body content.
-func (form *Form) GetContentBody() string {
+func (panel *Panel) GetContentBody() string {
return tplBody
}
// Execute generates the HTTP output of the form and writes it to the response writer.
-func (form *Form) Execute(w http.ResponseWriter) error {
+func (panel *Panel) Execute(w http.ResponseWriter) error {
type TemplateData struct {
}
tplData := TemplateData{}
- return form.htmlPanel.Execute(w, tplData)
+ return panel.htmlPanel.Execute(w, tplData)
}
-// NewForm creates a new web interface form.
-func NewForm(conf *config.Configuration, log *zerolog.Logger) (*Form, error) {
- form := &Form{}
+// 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.Wrapf(err, "unable to initialize the registration form")
+ return nil, errors.Wrapf(err, "unable to initialize the account panel")
}
return form, nil
}
diff --git a/pkg/siteacc/registration/template.go b/pkg/siteacc/account/template.go
similarity index 99%
rename from pkg/siteacc/registration/template.go
rename to pkg/siteacc/account/template.go
index ffc34fc4fa..d40e49c6d9 100644
--- a/pkg/siteacc/registration/template.go
+++ b/pkg/siteacc/account/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 registration
+package account
const tplJavaScript = `
function verifyForm(formData) {
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index cf5580aa56..b28a38b49a 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -28,14 +28,14 @@ import (
"github.com/rs/zerolog"
)
-// AdministrationPanel represents the web interface panel of the accounts service administration.
-type AdministrationPanel struct {
+// Panel represents the web interface panel of the accounts service administration.
+type Panel struct {
html.ContentProvider
htmlPanel *html.Panel
}
-func (panel *AdministrationPanel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+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 {
@@ -47,32 +47,32 @@ func (panel *AdministrationPanel) initialize(conf *config.Configuration, log *ze
}
// GetTitle returns the title of the htmlPanel.
-func (panel *AdministrationPanel) GetTitle() string {
- return "Administration Panel"
+func (panel *Panel) GetTitle() string {
+ return "ScienceMesh Administration Panel"
}
// GetCaption returns the caption which is displayed on the htmlPanel.
-func (panel *AdministrationPanel) GetCaption() string {
+func (panel *Panel) GetCaption() string {
return "Accounts ({{.Accounts | len}})"
}
// GetContentJavaScript delivers additional JavaScript code.
-func (panel *AdministrationPanel) GetContentJavaScript() string {
+func (panel *Panel) GetContentJavaScript() string {
return tplJavaScript
}
// GetContentStyleSheet delivers additional stylesheet code.
-func (panel *AdministrationPanel) GetContentStyleSheet() string {
+func (panel *Panel) GetContentStyleSheet() string {
return tplStyleSheet
}
// GetContentBody delivers the actual body content.
-func (panel *AdministrationPanel) GetContentBody() string {
+func (panel *Panel) GetContentBody() string {
return tplBody
}
// Execute generates the HTTP output of the htmlPanel and writes it to the response writer.
-func (panel *AdministrationPanel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
+func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
type TemplateData struct {
Accounts *data.Accounts
}
@@ -85,8 +85,8 @@ func (panel *AdministrationPanel) Execute(w http.ResponseWriter, accounts *data.
}
// NewPanel creates a new administration panel.
-func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*AdministrationPanel, error) {
- panel := &AdministrationPanel{}
+func NewPanel(conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
+ panel := &Panel{}
if err := panel.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the administration panel")
}
diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
index cc4be321e2..44fe68bb29 100644
--- a/pkg/siteacc/config/endpoints.go
+++ b/pkg/siteacc/config/endpoints.go
@@ -21,8 +21,8 @@ package config
const (
// EndpointAdministration is the endpoint path of the web interface administration panel.
EndpointAdministration = "/admin"
- // EndpointRegistration is the endpoint path of the web interface registration form.
- EndpointRegistration = "/register"
+ // EndpointAccount is the endpoint path of the web interface account panel.
+ EndpointAccount = "/account"
// EndpointGenerateAPIKey is the endpoint path of the API key generator.
EndpointGenerateAPIKey = "/generate-api-key"
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 4641cb4c07..6cde4ccce9 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -59,7 +59,7 @@ func getEndpoints() []endpoint {
endpoints := []endpoint{
// Form/panel endpoints
{config.EndpointAdministration, callAdministrationEndpoint, nil, false},
- {config.EndpointRegistration, callRegistrationEndpoint, nil, true},
+ {config.EndpointAccount, callAccountEndpoint, nil, true},
// Request endpoints
{config.EndpointGenerateAPIKey, callMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
{config.EndpointVerifyAPIKey, callMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
@@ -80,14 +80,14 @@ func getEndpoints() []endpoint {
func callAdministrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
if err := mngr.ShowAdministrationPanel(w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface administration adminPanel: %v", err)))
+ _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the administration panel: %v", err)))
}
}
-func callRegistrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowRegistrationForm(w); err != nil {
+func callAccountEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+ if err := mngr.ShowAccountPanel(w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the web interface registration registrationForm: %v", err)))
+ _, _ = w.Write([]byte(fmt.Sprintf("Unable to show the account panel: %v", err)))
}
}
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index f810656353..8668bc4133 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -24,11 +24,11 @@ import (
"sync"
"time"
+ "github.com/cs3org/reva/pkg/siteacc/account"
"github.com/cs3org/reva/pkg/siteacc/admin"
"github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/data"
"github.com/cs3org/reva/pkg/siteacc/email"
- "github.com/cs3org/reva/pkg/siteacc/registration"
"github.com/cs3org/reva/pkg/siteacc/sitereg"
"github.com/pkg/errors"
"github.com/rs/zerolog"
@@ -54,9 +54,10 @@ type Manager struct {
accounts data.Accounts
storage data.Storage
- adminPanel *admin.AdministrationPanel
- registrationForm *registration.Form
- smtp *smtpclient.SMTPCredentials
+ adminPanel *admin.Panel
+ accountPanel *account.Panel
+
+ smtp *smtpclient.SMTPCredentials
mutex sync.RWMutex
}
@@ -82,18 +83,18 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return errors.Wrap(err, "unable to create accounts storage")
}
- // Create the web interface adminPanel
+ // Create the admin panel
if pnl, err := admin.NewPanel(conf, log); err == nil {
mngr.adminPanel = pnl
} else {
- return errors.Wrap(err, "unable to create adminPanel")
+ return errors.Wrap(err, "unable to create the administration panel")
}
- // Create the web interface registrationForm
- if frm, err := registration.NewForm(conf, log); err == nil {
- mngr.registrationForm = frm
+ // Create the account panel
+ if pnl, err := account.NewPanel(conf, log); err == nil {
+ mngr.accountPanel = pnl
} else {
- return errors.Wrap(err, "unable to create registrationForm")
+ return errors.Wrap(err, "unable to create the account panel")
}
// Create the SMTP client
@@ -164,16 +165,16 @@ func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool)
return nil
}
-// ShowAdministrationPanel writes the adminPanel HTTP output directly to the response writer.
+// ShowAdministrationPanel writes the administration panel HTTP output directly to the response writer.
func (mngr *Manager) ShowAdministrationPanel(w http.ResponseWriter) error {
// The adminPanel only shows the stored accounts and offers actions through links, so let it use cloned data
accounts := mngr.CloneAccounts(true)
return mngr.adminPanel.Execute(w, &accounts)
}
-// ShowRegistrationForm writes the registration registrationForm HTTP output directly to the response writer.
-func (mngr *Manager) ShowRegistrationForm(w http.ResponseWriter) error {
- return mngr.registrationForm.Execute(w)
+// ShowAccountPanel writes the account panel HTTP output directly to the response writer.
+func (mngr *Manager) ShowAccountPanel(w http.ResponseWriter) error {
+ return mngr.accountPanel.Execute(w)
}
// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
From 951bfe543c2465358ffda110207dc77a53dc8f8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Tue, 6 Jul 2021 13:50:40 +0200
Subject: [PATCH 11/60] Add session management
---
.../config/http/services/siteacc/_index.md | 9 ++
examples/siteacc/siteacc.toml | 5 +
internal/http/services/siteacc/siteacc.go | 4 +
pkg/siteacc/account/panel.go | 4 +-
pkg/siteacc/admin/panel.go | 4 +-
pkg/siteacc/config/config.go | 4 +
pkg/siteacc/endpoints.go | 4 +-
pkg/siteacc/html/panel.go | 19 ++-
pkg/siteacc/html/session.go | 81 ++++++++++
pkg/siteacc/html/sessionmanager.go | 139 ++++++++++++++++++
pkg/siteacc/manager.go | 8 +-
11 files changed, 269 insertions(+), 12 deletions(-)
create mode 100644 pkg/siteacc/html/session.go
create mode 100644 pkg/siteacc/html/sessionmanager.go
diff --git a/docs/content/en/docs/config/http/services/siteacc/_index.md b/docs/content/en/docs/config/http/services/siteacc/_index.md
index d56f901c0f..7320466789 100644
--- a/docs/content/en/docs/config/http/services/siteacc/_index.md
+++ b/docs/content/en/docs/config/http/services/siteacc/_index.md
@@ -111,3 +111,12 @@ The registration service URL.
url = "https://iop.example.com/sitereg"
{{< /highlight >}}
{{% /dir %}}
+
+## Webserver settings
+{{% dir name="session_timeout" type="int" default="120" %}}
+The session timeout in seconds.
+{{< highlight toml >}}
+[http.services.siteacc.webserver]
+session_timeout = 600
+{{< /highlight >}}
+{{% /dir %}}
diff --git a/examples/siteacc/siteacc.toml b/examples/siteacc/siteacc.toml
index 093fe58f86..5e4d33031c 100644
--- a/examples/siteacc/siteacc.toml
+++ b/examples/siteacc/siteacc.toml
@@ -19,3 +19,8 @@ sender_mail = "science.mesh@example.com"
smtp_server = "mail.example.com"
smtp_port = 25
disable_auth = true
+
+# The webserver section defines various webserver-related settings
+[http.services.siteacc.webserver]
+session_timeout = 60
+
diff --git a/internal/http/services/siteacc/siteacc.go b/internal/http/services/siteacc/siteacc.go
index a57c92cd98..0c50cf3bf3 100644
--- a/internal/http/services/siteacc/siteacc.go
+++ b/internal/http/services/siteacc/siteacc.go
@@ -82,6 +82,10 @@ func applyDefaultConfig(conf *config.Configuration) {
if conf.Storage.Driver == "" {
conf.Storage.Driver = "file"
}
+
+ if conf.Webserver.SessionTimeout < 60 {
+ conf.Webserver.SessionTimeout = 120
+ }
}
// New returns a new Site Accounts service.
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index a5014af6a4..21c0de86d7 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -71,13 +71,13 @@ func (panel *Panel) GetContentBody() string {
}
// Execute generates the HTTP output of the form and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter) error {
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request) error {
type TemplateData struct {
}
tplData := TemplateData{}
- return panel.htmlPanel.Execute(w, tplData)
+ return panel.htmlPanel.Execute(w, r, tplData)
}
// NewPanel creates a new account panel.
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index b28a38b49a..9fba25bab1 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -72,7 +72,7 @@ func (panel *Panel) GetContentBody() string {
}
// Execute generates the HTTP output of the htmlPanel and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) error {
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, accounts *data.Accounts) error {
type TemplateData struct {
Accounts *data.Accounts
}
@@ -81,7 +81,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, accounts *data.Accounts) erro
Accounts: accounts,
}
- return panel.htmlPanel.Execute(w, tplData)
+ return panel.htmlPanel.Execute(w, r, tplData)
}
// NewPanel creates a new administration panel.
diff --git a/pkg/siteacc/config/config.go b/pkg/siteacc/config/config.go
index 15c23bb68f..cf802bc4ab 100644
--- a/pkg/siteacc/config/config.go
+++ b/pkg/siteacc/config/config.go
@@ -38,4 +38,8 @@ type Configuration struct {
SiteRegistration struct {
URL string `mapstructure:"url"`
} `mapstructure:"sitereg"`
+
+ Webserver struct {
+ SessionTimeout int `mapstructure:"session_timeout"`
+ } `mapstructure:"webserver"`
}
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 6cde4ccce9..8aca833860 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -78,14 +78,14 @@ func getEndpoints() []endpoint {
}
func callAdministrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowAdministrationPanel(w); err != nil {
+ if err := mngr.ShowAdministrationPanel(w, r); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the administration panel: %v", err)))
}
}
func callAccountEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowAccountPanel(w); err != nil {
+ if err := mngr.ShowAccountPanel(w, r); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the account panel: %v", err)))
}
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
index 06f74d273a..3f69e17c0a 100644
--- a/pkg/siteacc/html/panel.go
+++ b/pkg/siteacc/html/panel.go
@@ -35,7 +35,8 @@ type Panel struct {
provider ContentProvider
- tpl *template.Template
+ tpl *template.Template
+ sessions *SessionManager
}
func (panel *Panel) initialize(name string, provider ContentProvider, conf *config.Configuration, log *zerolog.Logger) error {
@@ -55,6 +56,7 @@ func (panel *Panel) initialize(name string, provider ContentProvider, conf *conf
panel.provider = provider
// Create the panel template
+ // TODO: Dynamic content; use session object for handling
content, err := panel.compile()
if err != nil {
return errors.Wrap(err, "error while compiling the panel template")
@@ -65,6 +67,13 @@ func (panel *Panel) initialize(name string, provider ContentProvider, conf *conf
return errors.Wrap(err, "error while parsing the panel template")
}
+ // Create the session mananger
+ sessions, err := NewSessionManager(name+"_session", conf, log)
+ if err != nil {
+ return errors.Wrap(err, "error while creating the session manager")
+ }
+ panel.sessions = sessions
+
return nil
}
@@ -83,7 +92,13 @@ func (panel *Panel) compile() (string, error) {
}
// Execute generates the HTTP output of the panel and writes it to the response writer.
-func (panel *Panel) Execute(w http.ResponseWriter, data interface{}) error {
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, data interface{}) error {
+ // TODO: Use returned session
+ _, err := panel.sessions.HandleRequest(w, r)
+ if err != nil {
+ return errors.Wrap(err, "an error occurred while handling sessions")
+ }
+
return panel.tpl.Execute(w, data)
}
diff --git a/pkg/siteacc/html/session.go b/pkg/siteacc/html/session.go
new file mode 100644
index 0000000000..521c9b6f5c
--- /dev/null
+++ b/pkg/siteacc/html/session.go
@@ -0,0 +1,81 @@
+// 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 html
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/pkg/errors"
+)
+
+// Session stores all data associated with an HTML session.
+type Session struct {
+ ID string
+ RemoteAddress string
+ Expires time.Time
+
+ Data map[string]interface{}
+
+ sessionCookieName string
+}
+
+// Save stores the session ID in a cookie using a response writer.
+func (sess *Session) Save(w http.ResponseWriter) {
+ http.SetCookie(w, &http.Cookie{
+ Name: sess.sessionCookieName,
+ Value: sess.ID,
+ Expires: sess.Expires,
+ })
+}
+
+// VerifyRequest checks whether the provided request matches the stored session.
+func (sess *Session) VerifyRequest(r *http.Request) error {
+ cookie, err := r.Cookie(sess.sessionCookieName)
+ if err != nil {
+ return errors.Wrap(err, "unable to retrieve client session ID")
+ }
+ if cookie.Value != sess.ID {
+ return errors.Errorf("the session ID doesn't match")
+ }
+
+ if r.RemoteAddr != sess.RemoteAddress {
+ return errors.Errorf("remote address has changed")
+ }
+
+ return nil
+}
+
+// HasExpired checks whether the session has reached is timeout.
+func (sess *Session) HasExpired() bool {
+ return time.Now().After(sess.Expires)
+}
+
+// NewSession creates a new session, giving it a random ID.
+func NewSession(name string, timeout time.Duration, r *http.Request) (*Session, error) {
+ session := &Session{
+ ID: uuid.NewString(),
+ RemoteAddress: r.RemoteAddr,
+ Expires: time.Now().Add(timeout),
+ Data: nil,
+ sessionCookieName: name,
+ }
+ return session, nil
+}
diff --git a/pkg/siteacc/html/sessionmanager.go b/pkg/siteacc/html/sessionmanager.go
new file mode 100644
index 0000000000..6674718d89
--- /dev/null
+++ b/pkg/siteacc/html/sessionmanager.go
@@ -0,0 +1,139 @@
+// 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 html
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog"
+)
+
+// SessionManager manages HTML sessions.
+type SessionManager struct {
+ conf *config.Configuration
+ log *zerolog.Logger
+
+ sessions map[string]*Session
+
+ sessionName string
+}
+
+func (mngr *SessionManager) initialize(name string, conf *config.Configuration, log *zerolog.Logger) error {
+ if name == "" {
+ return errors.Errorf("no session name provided")
+ }
+ mngr.sessionName = name
+
+ if conf == nil {
+ return errors.Errorf("no configuration provided")
+ }
+ mngr.conf = conf
+
+ if log == nil {
+ return errors.Errorf("no logger provided")
+ }
+ mngr.log = log
+
+ mngr.sessions = make(map[string]*Session, 100)
+
+ return nil
+}
+
+// HandleRequest performs all session-related tasks during an HTML request.
+func (mngr *SessionManager) HandleRequest(w http.ResponseWriter, r *http.Request) (*Session, error) {
+ var session *Session
+
+ // Try to get the session ID from the request; if none has been set yet, a new one will be assigned
+ cookie, err := r.Cookie(mngr.sessionName)
+ if err == nil {
+ session = mngr.findSession(cookie.Value)
+ if session != nil {
+ // Verify the request against the session: If it is invalid, return an error; if the session has expired, migrate to a new one; otherwise, just continue
+ if err := session.VerifyRequest(r); err == nil {
+ if session.HasExpired() {
+ session, err = mngr.migrateSession(session, r)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to migrate to a new session")
+ }
+ }
+ } else {
+ return nil, errors.Wrap(err, "invalid session")
+ }
+ }
+ } else if err != http.ErrNoCookie {
+ // The session cookie exists but seems to be invalid, so return an error
+ return nil, errors.Wrap(err, "unable to get the session ID from the client")
+ }
+
+ if session == nil {
+ // No session found for the client, so create a new one
+ session, err = mngr.createSession(r)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to assign a new session to the client")
+ }
+ }
+
+ // Store the session ID on the client side
+ session.Save(w)
+
+ return session, nil
+}
+
+func (mngr *SessionManager) createSession(r *http.Request) (*Session, error) {
+ session, err := NewSession(mngr.sessionName, time.Duration(mngr.conf.Webserver.SessionTimeout)*time.Second, r)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to create a new session")
+ }
+ mngr.sessions[session.ID] = session
+ return session, nil
+}
+
+func (mngr *SessionManager) findSession(id string) *Session {
+ if session, ok := mngr.sessions[id]; ok {
+ return session
+ }
+ return nil
+}
+
+func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (*Session, error) {
+ sessionNew, err := mngr.createSession(r)
+ if err != nil {
+ return nil, err
+ }
+
+ // Carry over the old session data, thus preserving the existing session
+ sessionNew.Data = session.Data
+
+ // Delete the old session
+ delete(mngr.sessions, session.ID)
+
+ return sessionNew, nil
+}
+
+// NewSessionManager creates a new session manager.
+func NewSessionManager(name string, conf *config.Configuration, log *zerolog.Logger) (*SessionManager, error) {
+ mngr := &SessionManager{}
+ if err := mngr.initialize(name, conf, log); err != nil {
+ return nil, errors.Wrapf(err, "unable to initialize the session manager")
+ }
+ return mngr, nil
+}
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index 8668bc4133..8fe8252cfb 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -166,15 +166,15 @@ func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool)
}
// ShowAdministrationPanel writes the administration panel HTTP output directly to the response writer.
-func (mngr *Manager) ShowAdministrationPanel(w http.ResponseWriter) error {
+func (mngr *Manager) ShowAdministrationPanel(w http.ResponseWriter, r *http.Request) error {
// The adminPanel only shows the stored accounts and offers actions through links, so let it use cloned data
accounts := mngr.CloneAccounts(true)
- return mngr.adminPanel.Execute(w, &accounts)
+ return mngr.adminPanel.Execute(w, r, &accounts)
}
// ShowAccountPanel writes the account panel HTTP output directly to the response writer.
-func (mngr *Manager) ShowAccountPanel(w http.ResponseWriter) error {
- return mngr.accountPanel.Execute(w)
+func (mngr *Manager) ShowAccountPanel(w http.ResponseWriter, r *http.Request) error {
+ return mngr.accountPanel.Execute(w, r)
}
// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
From 9836a6c1df85e5d7f64d81d421797bfc93e7ac1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Tue, 6 Jul 2021 15:34:51 +0200
Subject: [PATCH 12/60] Support multiple templates per panel
---
pkg/siteacc/account/panel.go | 26 +++++++--
pkg/siteacc/admin/panel.go | 32 ++++++++---
pkg/siteacc/html/panel.go | 91 ++++++++++++++++++++++----------
pkg/siteacc/html/provider.go | 8 +++
pkg/siteacc/html/session.go | 2 +-
pkg/siteacc/password/password.go | 2 -
6 files changed, 118 insertions(+), 43 deletions(-)
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index 21c0de86d7..6cbd21c443 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -29,11 +29,16 @@ import (
// Panel represents the account panel.
type Panel struct {
+ html.PanelProvider
html.ContentProvider
htmlPanel *html.Panel
}
+const (
+ templateRegister = "register"
+)
+
func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
// Create the internal HTML panel
htmlPanel, err := html.NewPanel("account-panel", panel, conf, log)
@@ -42,9 +47,19 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
panel.htmlPanel = htmlPanel
+ // Add all templates
+ if err := panel.htmlPanel.AddTemplate(templateRegister, panel); 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(*html.Session) string {
+ return templateRegister
+}
+
// GetTitle returns the title of the htmlPanel.
func (panel *Panel) GetTitle() string {
return "ScienceMesh Account Panel"
@@ -72,12 +87,13 @@ func (panel *Panel) GetContentBody() string {
// 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) error {
- type TemplateData struct {
- }
+ dataProvider := func(*html.Session) interface{} {
+ type TemplateData struct {
+ }
- tplData := TemplateData{}
-
- return panel.htmlPanel.Execute(w, r, tplData)
+ return TemplateData{}
+ }
+ return panel.htmlPanel.Execute(w, r, dataProvider)
}
// NewPanel creates a new account panel.
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index 9fba25bab1..a613872e2b 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -30,11 +30,16 @@ import (
// 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)
@@ -43,9 +48,19 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
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 {
+ return templateMain
+}
+
// GetTitle returns the title of the htmlPanel.
func (panel *Panel) GetTitle() string {
return "ScienceMesh Administration Panel"
@@ -73,15 +88,16 @@ func (panel *Panel) GetContentBody() string {
// 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, accounts *data.Accounts) error {
- type TemplateData struct {
- Accounts *data.Accounts
- }
-
- tplData := TemplateData{
- Accounts: accounts,
+ dataProvider := func(*html.Session) interface{} {
+ type TemplateData struct {
+ Accounts *data.Accounts
+ }
+
+ return TemplateData{
+ Accounts: accounts,
+ }
}
-
- return panel.htmlPanel.Execute(w, r, tplData)
+ return panel.htmlPanel.Execute(w, r, dataProvider)
}
// NewPanel creates a new administration panel.
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
index 3f69e17c0a..0d5e763010 100644
--- a/pkg/siteacc/html/panel.go
+++ b/pkg/siteacc/html/panel.go
@@ -28,18 +28,27 @@ import (
"github.com/rs/zerolog"
)
+type TemplateID = string
+
// Panel provides basic HTML panel functionality.
type Panel struct {
conf *config.Configuration
log *zerolog.Logger
- provider ContentProvider
+ name string
+
+ provider PanelProvider
- tpl *template.Template
- sessions *SessionManager
+ templates map[TemplateID]*template.Template
+ sessions *SessionManager
}
-func (panel *Panel) initialize(name string, provider ContentProvider, conf *config.Configuration, log *zerolog.Logger) error {
+func (panel *Panel) initialize(name string, provider PanelProvider, conf *config.Configuration, log *zerolog.Logger) error {
+ if name == "" {
+ return errors.Errorf("no name provided")
+ }
+ panel.name = name
+
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -51,21 +60,12 @@ func (panel *Panel) initialize(name string, provider ContentProvider, conf *conf
panel.log = log
if provider == nil {
- return errors.Errorf("no content provider provided")
+ return errors.Errorf("no panel provider provided")
}
panel.provider = provider
- // Create the panel template
- // TODO: Dynamic content; use session object for handling
- content, err := panel.compile()
- if err != nil {
- return errors.Wrap(err, "error while compiling the panel template")
- }
-
- panel.tpl = template.New(name)
- if _, err := panel.tpl.Parse(content); err != nil {
- return errors.Wrap(err, "error while parsing the panel template")
- }
+ // Create space for the panel templates
+ panel.templates = make(map[string]*template.Template, 5)
// Create the session mananger
sessions, err := NewSessionManager(name+"_session", conf, log)
@@ -77,33 +77,70 @@ func (panel *Panel) initialize(name string, provider ContentProvider, conf *conf
return nil
}
-func (panel *Panel) compile() (string, error) {
+func (panel *Panel) compile(provider ContentProvider) (string, error) {
content := panelTemplate
// Replace placeholders by the values provided by the content provider
- content = strings.ReplaceAll(content, "$(TITLE)", panel.provider.GetTitle())
- content = strings.ReplaceAll(content, "$(CAPTION)", panel.provider.GetCaption())
+ content = strings.ReplaceAll(content, "$(TITLE)", provider.GetTitle())
+ content = strings.ReplaceAll(content, "$(CAPTION)", provider.GetCaption())
- content = strings.ReplaceAll(content, "$(CONTENT_JAVASCRIPT)", panel.provider.GetContentJavaScript())
- content = strings.ReplaceAll(content, "$(CONTENT_STYLESHEET)", panel.provider.GetContentStyleSheet())
- content = strings.ReplaceAll(content, "$(CONTENT_BODY)", panel.provider.GetContentBody())
+ content = strings.ReplaceAll(content, "$(CONTENT_JAVASCRIPT)", provider.GetContentJavaScript())
+ content = strings.ReplaceAll(content, "$(CONTENT_STYLESHEET)", provider.GetContentStyleSheet())
+ content = strings.ReplaceAll(content, "$(CONTENT_BODY)", provider.GetContentBody())
return content, nil
}
+// AddTemplate adds and compiles a new template.
+func (panel *Panel) AddTemplate(name TemplateID, provider ContentProvider) error {
+ name = panel.getFullTemplateName(name)
+
+ if provider == nil {
+ return errors.Errorf("no content provider provided")
+ }
+
+ content, err := panel.compile(provider)
+ if err != nil {
+ return errors.Wrapf(err, "error while compiling panel template %v", name)
+ }
+
+ tpl := template.New(name)
+ if _, err := tpl.Parse(content); err != nil {
+ return errors.Wrapf(err, "error while parsing panel template %v", name)
+ }
+ panel.templates[name] = tpl
+
+ return 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, data interface{}) error {
- // TODO: Use returned session
- _, err := panel.sessions.HandleRequest(w, r)
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, dataProvider PanelDataProvider) error {
+ session, err := panel.sessions.HandleRequest(w, r)
if err != nil {
return errors.Wrap(err, "an error occurred while handling sessions")
}
- return panel.tpl.Execute(w, data)
+ tplName := panel.getFullTemplateName(panel.provider.GetActiveTemplate(session))
+ tpl, ok := panel.templates[tplName]
+ if !ok {
+ return errors.Errorf("template %v not found", tplName)
+ }
+
+ // If a data provider is specified, use it to get additional template data
+ var data interface{}
+ if dataProvider != nil {
+ data = dataProvider(session)
+ }
+
+ return tpl.Execute(w, data)
+}
+
+func (panel *Panel) getFullTemplateName(name string) string {
+ return panel.name + "-" + name
}
// NewPanel creates a new panel.
-func NewPanel(name string, provider ContentProvider, conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
+func NewPanel(name string, provider PanelProvider, conf *config.Configuration, log *zerolog.Logger) (*Panel, error) {
panel := &Panel{}
if err := panel.initialize(name, provider, conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the panel")
diff --git a/pkg/siteacc/html/provider.go b/pkg/siteacc/html/provider.go
index 668b920568..e6142bc784 100644
--- a/pkg/siteacc/html/provider.go
+++ b/pkg/siteacc/html/provider.go
@@ -18,6 +18,14 @@
package html
+// PanelProvider handles general panel tasks.
+type PanelProvider interface {
+ // GetActiveTemplate returns the name of the active template.
+ GetActiveTemplate(*Session) string
+}
+
+type PanelDataProvider = func(*Session) interface{}
+
// ContentProvider defines various methods for HTML content providers.
type ContentProvider interface {
// GetTitle returns the title of the panel.
diff --git a/pkg/siteacc/html/session.go b/pkg/siteacc/html/session.go
index 521c9b6f5c..c9f13be389 100644
--- a/pkg/siteacc/html/session.go
+++ b/pkg/siteacc/html/session.go
@@ -57,7 +57,7 @@ func (sess *Session) VerifyRequest(r *http.Request) error {
}
if r.RemoteAddr != sess.RemoteAddress {
- return errors.Errorf("remote address has changed")
+ return errors.Errorf("remote address has changed (%v != %v)", r.RemoteAddr, sess.RemoteAddress)
}
return nil
diff --git a/pkg/siteacc/password/password.go b/pkg/siteacc/password/password.go
index 049252c346..a9a59467e4 100644
--- a/pkg/siteacc/password/password.go
+++ b/pkg/siteacc/password/password.go
@@ -19,7 +19,6 @@
package password
import (
- "fmt"
"strings"
"github.com/pkg/errors"
@@ -46,7 +45,6 @@ func (password *Password) Set(pwd string) error {
return errors.Wrap(err, "unable to generate password hash")
}
password.Value = string(pwdData)
- fmt.Println(password.Value)
return nil
}
From 4174708c78b76840b7444c865906a4bf73fa7d31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 7 Jul 2021 13:57:26 +0200
Subject: [PATCH 13/60] Add login form (WIP)
---
pkg/siteacc/account/login/login.go | 50 +++++++++
pkg/siteacc/account/login/template.go | 106 ++++++++++++++++++
pkg/siteacc/account/panel.go | 49 ++++----
.../account/registration/registration.go | 50 +++++++++
.../account/{ => registration}/template.go | 5 +-
pkg/siteacc/admin/panel.go | 2 +-
pkg/siteacc/endpoints.go | 5 +-
pkg/siteacc/html/panel.go | 9 +-
pkg/siteacc/html/provider.go | 2 +-
9 files changed, 245 insertions(+), 33 deletions(-)
create mode 100644 pkg/siteacc/account/login/login.go
create mode 100644 pkg/siteacc/account/login/template.go
create mode 100644 pkg/siteacc/account/registration/registration.go
rename pkg/siteacc/account/{ => registration}/template.go (98%)
diff --git a/pkg/siteacc/account/login/login.go b/pkg/siteacc/account/login/login.go
new file mode 100644
index 0000000000..237ff5dcb0
--- /dev/null
+++ b/pkg/siteacc/account/login/login.go
@@ -0,0 +1,50 @@
+// 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 login
+
+import "github.com/cs3org/reva/pkg/siteacc/html"
+
+type PanelTemplate struct {
+ html.ContentProvider
+}
+
+// GetTitle returns the title of the htmlPanel.
+func (template *PanelTemplate) GetTitle() string {
+ return "ScienceMesh Account Login"
+}
+
+// GetCaption returns the caption which is displayed on the htmlPanel.
+func (template *PanelTemplate) GetCaption() string {
+ return "Login to your ScienceMesh Account!"
+}
+
+// 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/account/login/template.go b/pkg/siteacc/account/login/template.go
new file mode 100644
index 0000000000..19cc7bb069
--- /dev/null
+++ b/pkg/siteacc/account/login/template.go
@@ -0,0 +1,106 @@
+// 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 login
+
+const tplJavaScript = `
+function verifyForm(formData) {
+ if (formData.get("email") == "") {
+ setState(STATE_ERROR, "Please enter your email address.", "form", "email", true);
+ return false;
+ }
+
+ if (formData.get("password") == "") {
+ setState(STATE_ERROR, "Please enter your password.", "form", "password", true);
+ return false;
+ }
+
+ return true;
+}
+
+function handleAction(action) {
+ const formData = new FormData(document.querySelector("form"));
+ if (!verifyForm(formData)) {
+ return;
+ }
+
+ setState(STATE_STATUS, "Logging in... this should only take a moment.", "form", null, false);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", action);
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState === XMLHttpRequest.DONE) {
+ if (this.status == 200) {
+ setState(STATE_SUCCESS, "Your login was successful! Redirecting...");
+ window.location.replace("?manage");
+ } else {
+ var resp = JSON.parse(this.responseText);
+ setState(STATE_ERROR, "An error occurred while trying to login your account:" + resp.error + " ", "form", null, true);
+ }
+ }
+ }
+
+ var postData = {
+ "email": formData.get("email"),
+ "password": {
+ "value": formData.get("password")
+ }
+ };
+
+ xhr.send(JSON.stringify(postData));
+}
+`
+
+const tplStyleSheet = `
+html * {
+ font-family: arial !important;
+}
+
+.mandatory {
+ color: red;
+ font-weight: bold;
+}
+`
+
+const tplBody = `
+
+
Login to your ScienceMesh account using the form below.
+
+
+
+
+
Don't' have an account yet? Register here .
+
+`
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index 6cbd21c443..b66da7a3cf 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -21,6 +21,8 @@ package account
import (
"net/http"
+ "github.com/cs3org/reva/pkg/siteacc/account/login"
+ "github.com/cs3org/reva/pkg/siteacc/account/registration"
"github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/html"
"github.com/pkg/errors"
@@ -30,13 +32,13 @@ import (
// Panel represents the account panel.
type Panel struct {
html.PanelProvider
- html.ContentProvider
htmlPanel *html.Panel
}
const (
- templateRegister = "register"
+ templateLogin = "login"
+ templateRegistration = "register"
)
func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger) error {
@@ -48,7 +50,11 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
panel.htmlPanel = htmlPanel
// Add all templates
- if err := panel.htmlPanel.AddTemplate(templateRegister, panel); err != nil {
+ 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(templateRegistration, ®istration.PanelTemplate{}); err != nil {
return errors.Wrap(err, "unable to create the registration template")
}
@@ -56,33 +62,20 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// GetActiveTemplate returns the name of the active template.
-func (panel *Panel) GetActiveTemplate(*html.Session) string {
- return templateRegister
-}
-
-// GetTitle returns the title of the htmlPanel.
-func (panel *Panel) GetTitle() string {
- return "ScienceMesh Account Panel"
-}
-
-// GetCaption returns the caption which is displayed on the htmlPanel.
-func (panel *Panel) GetCaption() string {
- return "Welcome to the ScienceMesh Account Registration!"
-}
-
-// GetContentJavaScript delivers additional JavaScript code.
-func (panel *Panel) GetContentJavaScript() string {
- return tplJavaScript
-}
+func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string {
+ template := templateLogin // TODO: Check if user is logged in
+
+ // Invalid paths are just ignored and redirected to the login/main page
+ switch path {
+ case templateLogin:
+ case templateRegistration:
+ template = path
+ }
-// GetContentStyleSheet delivers additional stylesheet code.
-func (panel *Panel) GetContentStyleSheet() string {
- return tplStyleSheet
-}
+ // TODO: Check path access
+ // TODO: If user is logged in and path == login, redirect to main
-// GetContentBody delivers the actual body content.
-func (panel *Panel) GetContentBody() string {
- return tplBody
+ return template
}
// Execute generates the HTTP output of the form and writes it to the response writer.
diff --git a/pkg/siteacc/account/registration/registration.go b/pkg/siteacc/account/registration/registration.go
new file mode 100644
index 0000000000..f8720a7e7b
--- /dev/null
+++ b/pkg/siteacc/account/registration/registration.go
@@ -0,0 +1,50 @@
+// 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 registration
+
+import "github.com/cs3org/reva/pkg/siteacc/html"
+
+type PanelTemplate struct {
+ html.ContentProvider
+}
+
+// GetTitle returns the title of the htmlPanel.
+func (template *PanelTemplate) GetTitle() string {
+ return "ScienceMesh Account Registration"
+}
+
+// GetCaption returns the caption which is displayed on the htmlPanel.
+func (template *PanelTemplate) GetCaption() string {
+ return "Welcome to the ScienceMesh Account Registration!"
+}
+
+// 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/account/template.go b/pkg/siteacc/account/registration/template.go
similarity index 98%
rename from pkg/siteacc/account/template.go
rename to pkg/siteacc/account/registration/template.go
index d40e49c6d9..d58e3ce184 100644
--- a/pkg/siteacc/account/template.go
+++ b/pkg/siteacc/account/registration/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 account
+package registration
const tplJavaScript = `
function verifyForm(formData) {
@@ -169,4 +169,7 @@ const tplBody = `
+
+
Already have an account? Login here .
+
`
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index a613872e2b..9e8bd50042 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -57,7 +57,7 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// GetActiveTemplate returns the name of the active template.
-func (panel *Panel) GetActiveTemplate(*html.Session) string {
+func (panel *Panel) GetActiveTemplate(*html.Session, string) string {
return templateMain
}
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 8aca833860..781194f7f6 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -60,17 +60,20 @@ func getEndpoints() []endpoint {
// Form/panel endpoints
{config.EndpointAdministration, callAdministrationEndpoint, nil, false},
{config.EndpointAccount, callAccountEndpoint, nil, true},
- // Request endpoints
+ // API key endpoints
{config.EndpointGenerateAPIKey, callMethodEndpoint, createMethodCallbacks(handleGenerateAPIKey, nil), false},
{config.EndpointVerifyAPIKey, callMethodEndpoint, createMethodCallbacks(handleVerifyAPIKey, nil), false},
{config.EndpointAssignAPIKey, callMethodEndpoint, createMethodCallbacks(nil, handleAssignAPIKey), false},
+ // General account endpoints
{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.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
+ // Authorization endpoints
{config.EndpointAuthorize, callMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
{config.EndpointIsAuthorized, callMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
+ // Account site endpoints
{config.EndpointUnregisterSite, callMethodEndpoint, createMethodCallbacks(nil, handleUnregisterSite), false},
}
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
index 0d5e763010..a0782c3126 100644
--- a/pkg/siteacc/html/panel.go
+++ b/pkg/siteacc/html/panel.go
@@ -43,6 +43,10 @@ type Panel struct {
sessions *SessionManager
}
+const (
+ pathParameterName = "path"
+)
+
func (panel *Panel) initialize(name string, provider PanelProvider, conf *config.Configuration, log *zerolog.Logger) error {
if name == "" {
return errors.Errorf("no name provided")
@@ -120,7 +124,10 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, dataProvider
return errors.Wrap(err, "an error occurred while handling sessions")
}
- tplName := panel.getFullTemplateName(panel.provider.GetActiveTemplate(session))
+ // Get the path query parameter; the panel provider may use this to determine the template to use
+ path := r.URL.Query().Get(pathParameterName)
+
+ tplName := panel.getFullTemplateName(panel.provider.GetActiveTemplate(session, path))
tpl, ok := panel.templates[tplName]
if !ok {
return errors.Errorf("template %v not found", tplName)
diff --git a/pkg/siteacc/html/provider.go b/pkg/siteacc/html/provider.go
index e6142bc784..2dc5afb5d2 100644
--- a/pkg/siteacc/html/provider.go
+++ b/pkg/siteacc/html/provider.go
@@ -21,7 +21,7 @@ package html
// PanelProvider handles general panel tasks.
type PanelProvider interface {
// GetActiveTemplate returns the name of the active template.
- GetActiveTemplate(*Session) string
+ GetActiveTemplate(*Session, string) string
}
type PanelDataProvider = func(*Session) interface{}
From 6f89beb299b3955efa6a01a928e8ed806b216df2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 7 Jul 2021 15:03:35 +0200
Subject: [PATCH 14/60] Add user authentication endpoint
---
pkg/siteacc/account/login/template.go | 2 +-
pkg/siteacc/config/endpoints.go | 3 +++
pkg/siteacc/endpoints.go | 16 ++++++++++++++++
pkg/siteacc/manager.go | 21 ++++++++++++++++++---
4 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index 19cc7bb069..c2c3e9247b 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -96,7 +96,7 @@ const tplBody = `
Reset
- Login
+ Login
diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
index 44fe68bb29..d8c12bfb43 100644
--- a/pkg/siteacc/config/endpoints.go
+++ b/pkg/siteacc/config/endpoints.go
@@ -43,6 +43,9 @@ const (
// EndpointRemove is the endpoint path for account removal.
EndpointRemove = "/remove"
+ // EndpointAuthenticate is the endpoint path for user authentication.
+ EndpointAuthenticate = "/authenticate"
+
// EndpointAuthorize is the endpoint path for account authorization.
EndpointAuthorize = "/authorize"
// EndpointIsAuthorized is the endpoint path used to check the authorization status of an account.
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 781194f7f6..62142553fa 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -70,6 +70,8 @@ func getEndpoints() []endpoint {
{config.EndpointCreate, callMethodEndpoint, createMethodCallbacks(nil, handleCreate), true},
{config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false},
{config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
+ // Authentication endpoints
+ {config.EndpointAuthenticate, callMethodEndpoint, createMethodCallbacks(nil, handleAuthenticate), true},
// Authorization endpoints
{config.EndpointAuthorize, callMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
{config.EndpointIsAuthorized, callMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
@@ -270,6 +272,20 @@ func handleUnregisterSite(mngr *Manager, values url.Values, body []byte) (interf
return nil, nil
}
+func handleAuthenticate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+ account, err := unmarshalRequestData(body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Authenticate the user through the account manager
+ if _, err := mngr.AuthenticateUser(account.Email, account.Password.Value); err != nil {
+ return nil, errors.Wrap(err, "unable to authenticate user")
+ }
+
+ return nil, nil
+}
+
func handleAuthorize(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager.go
index 8fe8252cfb..a7d73336f0 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager.go
@@ -24,7 +24,7 @@ import (
"sync"
"time"
- "github.com/cs3org/reva/pkg/siteacc/account"
+ accpanel "github.com/cs3org/reva/pkg/siteacc/account"
"github.com/cs3org/reva/pkg/siteacc/admin"
"github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/data"
@@ -55,7 +55,7 @@ type Manager struct {
storage data.Storage
adminPanel *admin.Panel
- accountPanel *account.Panel
+ accountPanel *accpanel.Panel
smtp *smtpclient.SMTPCredentials
@@ -91,7 +91,7 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
}
// Create the account panel
- if pnl, err := account.NewPanel(conf, log); err == nil {
+ if pnl, err := accpanel.NewPanel(conf, log); err == nil {
mngr.accountPanel = pnl
} else {
return errors.Wrap(err, "unable to create the account panel")
@@ -350,6 +350,21 @@ func (mngr *Manager) CloneAccounts(erasePasswords bool) data.Accounts {
return clones
}
+// AuthenticateUser tries to authenticate a given username/password pair. On success, the corresponding user account is returned.
+func (mngr *Manager) AuthenticateUser(name, password string) (*data.Account, error) {
+ account, err := mngr.findAccount(FindByEmail, name)
+ if err != nil {
+ return nil, errors.Wrap(err, "no account with the specified email exists")
+ }
+
+ // Verify the provided password
+ if !account.Password.Compare(password) {
+ return nil, errors.Errorf("invalid password")
+ }
+
+ return account, nil
+}
+
func newManager(conf *config.Configuration, log *zerolog.Logger) (*Manager, error) {
mngr := &Manager{}
if err := mngr.initialize(conf, log); err != nil {
From a94675fadc8a96109ae03314d21898cfec27f0da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Thu, 8 Jul 2021 14:51:06 +0200
Subject: [PATCH 15/60] Restructure session management and support login
---
pkg/mentix/utils/network/network.go | 3 +-
pkg/siteacc/account/login/template.go | 5 +-
pkg/siteacc/account/panel.go | 13 ++-
pkg/siteacc/admin/panel.go | 9 +-
pkg/siteacc/config/endpoints.go | 6 +-
pkg/siteacc/endpoints.go | 94 ++++++++++---------
pkg/siteacc/html/panel.go | 23 ++---
pkg/siteacc/html/provider.go | 7 ++
pkg/siteacc/html/session.go | 7 +-
pkg/siteacc/html/sessionmanager.go | 39 ++++----
.../{manager.go => manager/accmanager.go} | 86 ++++-------------
pkg/siteacc/manager/usersmanager.go | 83 ++++++++++++++++
pkg/siteacc/siteacc.go | 76 ++++++++++++++-
13 files changed, 288 insertions(+), 163 deletions(-)
rename pkg/siteacc/{manager.go => manager/accmanager.go} (75%)
create mode 100644 pkg/siteacc/manager/usersmanager.go
diff --git a/pkg/mentix/utils/network/network.go b/pkg/mentix/utils/network/network.go
index 6185ce8fde..80c3c96368 100644
--- a/pkg/mentix/utils/network/network.go
+++ b/pkg/mentix/utils/network/network.go
@@ -79,11 +79,12 @@ func queryEndpoint(method string, endpointURL *url.URL, auth *BasicAuth, checkSt
if err != nil {
return nil, fmt.Errorf("unable to get data from endpoint: %v", err)
}
+ defer resp.Body.Close()
+
if checkStatus && resp.StatusCode >= 400 {
return nil, fmt.Errorf("invalid response received: %v", resp.Status)
}
- defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return body, nil
}
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index c2c3e9247b..d8823cbe0a 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -48,8 +48,7 @@ function handleAction(action) {
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE) {
if (this.status == 200) {
- setState(STATE_SUCCESS, "Your login was successful! Redirecting...");
- window.location.replace("?manage");
+ setState(STATE_SUCCESS, "Your login was successful!");
} else {
var resp = JSON.parse(this.responseText);
setState(STATE_ERROR, "An error occurred while trying to login your account:" + resp.error + " ", "form", null, true);
@@ -96,7 +95,7 @@ const tplBody = `
Reset
- Login
+ Login
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index b66da7a3cf..494f9d9924 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -78,15 +78,24 @@ func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string
return template
}
+// PreExecute is called before the actual template is being executed.
+func (panel *Panel) PreExecute(*html.Session, string, *http.Request) error {
+ return 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) error {
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *html.Session) error {
dataProvider := func(*html.Session) interface{} {
type TemplateData struct {
}
return TemplateData{}
}
- return panel.htmlPanel.Execute(w, r, dataProvider)
+ return panel.htmlPanel.Execute(w, r, session, dataProvider)
+}
+
+func (panel *Panel) executeLogin(session *html.Session, r *http.Request) error {
+ return nil
}
// NewPanel creates a new account panel.
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index 9e8bd50042..1389ccb052 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -86,8 +86,13 @@ 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.Request) error {
+ return 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, accounts *data.Accounts) error {
+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
@@ -97,7 +102,7 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, accounts *da
Accounts: accounts,
}
}
- return panel.htmlPanel.Execute(w, r, dataProvider)
+ return panel.htmlPanel.Execute(w, r, session, dataProvider)
}
// NewPanel creates a new administration panel.
diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
index d8c12bfb43..56d87fe8e2 100644
--- a/pkg/siteacc/config/endpoints.go
+++ b/pkg/siteacc/config/endpoints.go
@@ -43,8 +43,10 @@ const (
// EndpointRemove is the endpoint path for account removal.
EndpointRemove = "/remove"
- // EndpointAuthenticate is the endpoint path for user authentication.
- EndpointAuthenticate = "/authenticate"
+ // EndpointLogin is the endpoint path for (internal) user login.
+ EndpointLogin = "/login"
+ // EndpointLogout is the endpoint path for (internal) user logout.
+ EndpointLogout = "/logout"
// EndpointAuthorize is the endpoint path for account authorization.
EndpointAuthorize = "/authorize"
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 62142553fa..74d010bde3 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -29,14 +29,15 @@ import (
"github.com/cs3org/reva/pkg/mentix/key"
"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"
)
-type methodCallback = func(*Manager, url.Values, []byte) (interface{}, error)
+type methodCallback = func(*SiteAccounts, url.Values, []byte, *html.Session) (interface{}, error)
type endpoint struct {
Path string
- Handler func(*Manager, endpoint, http.ResponseWriter, *http.Request)
+ Handler func(*SiteAccounts, endpoint, http.ResponseWriter, *http.Request, *html.Session)
MethodCallbacks map[string]methodCallback
IsPublic bool
}
@@ -70,8 +71,9 @@ func getEndpoints() []endpoint {
{config.EndpointCreate, callMethodEndpoint, createMethodCallbacks(nil, handleCreate), true},
{config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false},
{config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
- // Authentication endpoints
- {config.EndpointAuthenticate, callMethodEndpoint, createMethodCallbacks(nil, handleAuthenticate), true},
+ // Login endpoints
+ {config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), false},
+ {config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), false},
// Authorization endpoints
{config.EndpointAuthorize, callMethodEndpoint, createMethodCallbacks(nil, handleAuthorize), false},
{config.EndpointIsAuthorized, callMethodEndpoint, createMethodCallbacks(handleIsAuthorized, nil), false},
@@ -82,21 +84,21 @@ func getEndpoints() []endpoint {
return endpoints
}
-func callAdministrationEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowAdministrationPanel(w, r); err != nil {
+func callAdministrationEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
+ if err := siteacc.ShowAdministrationPanel(w, r, session); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the administration panel: %v", err)))
}
}
-func callAccountEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
- if err := mngr.ShowAccountPanel(w, r); err != nil {
+func callAccountEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
+ if err := siteacc.ShowAccountPanel(w, r, session); err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the account panel: %v", err)))
}
}
-func callMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *http.Request) {
+func callMethodEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
// Every request to the accounts service results in a standardized JSON response
type Response struct {
Success bool `json:"success"`
@@ -117,7 +119,7 @@ func callMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *ht
if method == r.Method {
body, _ := ioutil.ReadAll(r.Body)
- if respData, err := cb(mngr, r.URL.Query(), body); err == nil {
+ if respData, err := cb(siteacc, r.URL.Query(), body, session); err == nil {
resp.Success = true
resp.Error = ""
resp.Data = respData
@@ -139,7 +141,7 @@ func callMethodEndpoint(mngr *Manager, ep endpoint, w http.ResponseWriter, r *ht
_, _ = w.Write(jsonData)
}
-func handleGenerateAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleGenerateAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
email := values.Get("email")
flags := key.FlagDefault
@@ -158,7 +160,7 @@ func handleGenerateAPIKey(mngr *Manager, values url.Values, body []byte) (interf
return map[string]string{"apiKey": apiKey}, nil
}
-func handleVerifyAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleVerifyAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
apiKey := values.Get("apiKey")
email := values.Get("email")
@@ -177,7 +179,7 @@ func handleVerifyAPIKey(mngr *Manager, values url.Values, body []byte) (interfac
return nil, nil
}
-func handleAssignAPIKey(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleAssignAPIKey(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
@@ -188,105 +190,111 @@ func handleAssignAPIKey(mngr *Manager, values url.Values, body []byte) (interfac
flags |= key.FlagScienceMesh
}
- // Assign a new API key to the account through the account manager
- if err := mngr.AssignAPIKeyToAccount(account, flags); err != nil {
+ // Assign a new API key to the account through the account accountsManager
+ if err := siteacc.AccountsManager().AssignAPIKeyToAccount(account, flags); err != nil {
return nil, errors.Wrap(err, "unable to assign API key")
}
return nil, nil
}
-func handleList(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
- return mngr.CloneAccounts(true), nil
+func handleList(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
+ return siteacc.AccountsManager().CloneAccounts(true), nil
}
-func handleFind(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
- account, err := findAccount(mngr, values.Get("by"), values.Get("value"))
+func handleFind(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
+ account, err := findAccount(siteacc, values.Get("by"), values.Get("value"))
if err != nil {
return nil, err
}
return map[string]interface{}{"account": account.Clone(true)}, nil
}
-func handleCreate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleCreate(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
}
- // Create a new account through the account manager
- if err := mngr.CreateAccount(account); err != nil {
+ // Create a new account through the account accountsManager
+ if err := siteacc.AccountsManager().CreateAccount(account); err != nil {
return nil, errors.Wrap(err, "unable to create account")
}
return nil, nil
}
-func handleUpdate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleUpdate(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
}
- // Update the account through the account manager; only the basic data of an account can be updated through this requestHandler
- if err := mngr.UpdateAccount(account, false); err != nil {
+ // Update the account through the account accountsManager; only the basic data of an account can be updated through this requestHandler
+ if err := siteacc.AccountsManager().UpdateAccount(account, false); err != nil {
return nil, errors.Wrap(err, "unable to update account")
}
return nil, nil
}
-func handleRemove(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleRemove(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
}
- // Remove the account through the account manager
- if err := mngr.RemoveAccount(account); err != nil {
+ // Remove the account through the account accountsManager
+ if err := siteacc.AccountsManager().RemoveAccount(account); err != nil {
return nil, errors.Wrap(err, "unable to remove account")
}
return nil, nil
}
-func handleIsAuthorized(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
- account, err := findAccount(mngr, values.Get("by"), values.Get("value"))
+func handleIsAuthorized(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
+ account, err := findAccount(siteacc, values.Get("by"), values.Get("value"))
if err != nil {
return nil, err
}
return account.Data.Authorized, nil
}
-func handleUnregisterSite(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleUnregisterSite(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
}
- // Unregister the account's site through the account manager
- if err := mngr.UnregisterAccountSite(account); err != nil {
+ // Unregister the account's site through the account accountsManager
+ if err := siteacc.AccountsManager().UnregisterAccountSite(account); err != nil {
return nil, errors.Wrap(err, "unable to unregister the site of the given account")
}
return nil, nil
}
-func handleAuthenticate(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleLogin(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
}
- // Authenticate the user through the account manager
- if _, err := mngr.AuthenticateUser(account.Email, account.Password.Value); err != nil {
- return nil, errors.Wrap(err, "unable to authenticate user")
+ // Login the user through the users manager
+ if err := siteacc.UsersManager().LoginUser(account.Email, account.Password.Value, session); err != nil {
+ return nil, errors.Wrap(err, "unable to login user")
}
return nil, nil
}
-func handleAuthorize(mngr *Manager, values url.Values, body []byte) (interface{}, error) {
+func handleLogout(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
+ // Logout the user through the users manager
+ siteacc.UsersManager().LogoutUser(session)
+ return nil, nil
+}
+
+func handleAuthorize(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
account, err := unmarshalRequestData(body)
if err != nil {
return nil, err
@@ -305,8 +313,8 @@ func handleAuthorize(mngr *Manager, values url.Values, body []byte) (interface{}
return nil, errors.Errorf("unsupported authorization status %v", val[0])
}
- // Authorize the account through the account manager
- if err := mngr.AuthorizeAccount(account, authorize); err != nil {
+ // Authorize the account through the account accountsManager
+ if err := siteacc.AccountsManager().AuthorizeAccount(account, authorize); err != nil {
return nil, errors.Wrap(err, "unable to (un)authorize account")
}
} else {
@@ -324,13 +332,13 @@ func unmarshalRequestData(body []byte) (*data.Account, error) {
return account, nil
}
-func findAccount(mngr *Manager, by string, value string) (*data.Account, error) {
+func findAccount(siteacc *SiteAccounts, by string, value string) (*data.Account, error) {
if len(by) == 0 && len(value) == 0 {
return nil, errors.Errorf("missing search criteria")
}
- // Find the account using the account manager
- account, err := mngr.FindAccount(by, value)
+ // Find the account using the account accountsManager
+ account, err := siteacc.AccountsManager().FindAccount(by, value)
if err != nil {
return nil, errors.Wrap(err, "user not found")
}
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
index a0782c3126..8b120ed177 100644
--- a/pkg/siteacc/html/panel.go
+++ b/pkg/siteacc/html/panel.go
@@ -40,7 +40,6 @@ type Panel struct {
provider PanelProvider
templates map[TemplateID]*template.Template
- sessions *SessionManager
}
const (
@@ -71,13 +70,6 @@ func (panel *Panel) initialize(name string, provider PanelProvider, conf *config
// Create space for the panel templates
panel.templates = make(map[string]*template.Template, 5)
- // Create the session mananger
- sessions, err := NewSessionManager(name+"_session", conf, log)
- if err != nil {
- return errors.Wrap(err, "error while creating the session manager")
- }
- panel.sessions = sessions
-
return nil
}
@@ -118,16 +110,12 @@ func (panel *Panel) AddTemplate(name TemplateID, provider ContentProvider) error
}
// 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, dataProvider PanelDataProvider) error {
- session, err := panel.sessions.HandleRequest(w, r)
- if err != nil {
- return errors.Wrap(err, "an error occurred while handling sessions")
- }
-
+func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *Session, dataProvider PanelDataProvider) error {
// Get the path query parameter; the panel provider may use this to determine the template to use
path := r.URL.Query().Get(pathParameterName)
- tplName := panel.getFullTemplateName(panel.provider.GetActiveTemplate(session, path))
+ actTpl := panel.provider.GetActiveTemplate(session, path)
+ tplName := panel.getFullTemplateName(actTpl)
tpl, ok := panel.templates[tplName]
if !ok {
return errors.Errorf("template %v not found", tplName)
@@ -139,6 +127,11 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, dataProvider
data = dataProvider(session)
}
+ // Perform the pre-execution phase in which the panel provider can intercept the actual execution
+ if err := panel.provider.PreExecute(session, actTpl, r); err != nil {
+ return errors.Wrapf(err, "pre-execution of template %v failed", tplName)
+ }
+
return tpl.Execute(w, data)
}
diff --git a/pkg/siteacc/html/provider.go b/pkg/siteacc/html/provider.go
index 2dc5afb5d2..0f70ad521d 100644
--- a/pkg/siteacc/html/provider.go
+++ b/pkg/siteacc/html/provider.go
@@ -18,10 +18,17 @@
package html
+import (
+ "net/http"
+)
+
// PanelProvider handles general panel tasks.
type PanelProvider interface {
// GetActiveTemplate returns the name of the active template.
GetActiveTemplate(*Session, string) string
+
+ // PreExecute is called before the actual template is being executed.
+ PreExecute(*Session, string, *http.Request) error
}
type PanelDataProvider = func(*Session) interface{}
diff --git a/pkg/siteacc/html/session.go b/pkg/siteacc/html/session.go
index c9f13be389..03e34e9f7b 100644
--- a/pkg/siteacc/html/session.go
+++ b/pkg/siteacc/html/session.go
@@ -22,6 +22,7 @@ import (
"net/http"
"time"
+ "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/google/uuid"
"github.com/pkg/errors"
)
@@ -32,6 +33,8 @@ type Session struct {
RemoteAddress string
Expires time.Time
+ LoggedInUser *data.Account
+
Data map[string]interface{}
sessionCookieName string
@@ -69,7 +72,7 @@ func (sess *Session) HasExpired() bool {
}
// NewSession creates a new session, giving it a random ID.
-func NewSession(name string, timeout time.Duration, r *http.Request) (*Session, error) {
+func NewSession(name string, timeout time.Duration, r *http.Request) *Session {
session := &Session{
ID: uuid.NewString(),
RemoteAddress: r.RemoteAddr,
@@ -77,5 +80,5 @@ func NewSession(name string, timeout time.Duration, r *http.Request) (*Session,
Data: nil,
sessionCookieName: name,
}
- return session, nil
+ return session
}
diff --git a/pkg/siteacc/html/sessionmanager.go b/pkg/siteacc/html/sessionmanager.go
index 6674718d89..52b2fed1fa 100644
--- a/pkg/siteacc/html/sessionmanager.go
+++ b/pkg/siteacc/html/sessionmanager.go
@@ -58,53 +58,51 @@ func (mngr *SessionManager) initialize(name string, conf *config.Configuration,
return nil
}
-// HandleRequest performs all session-related tasks during an HTML request.
+// HandleRequest performs all session-related tasks during an HTML request. Always returns a valid session object.
func (mngr *SessionManager) HandleRequest(w http.ResponseWriter, r *http.Request) (*Session, error) {
var session *Session
+ var sessionErr error
// Try to get the session ID from the request; if none has been set yet, a new one will be assigned
cookie, err := r.Cookie(mngr.sessionName)
if err == nil {
session = mngr.findSession(cookie.Value)
if session != nil {
- // Verify the request against the session: If it is invalid, return an error; if the session has expired, migrate to a new one; otherwise, just continue
+ // Verify the request against the session: If it is invalid, set an error; if the session has expired, migrate to a new one; otherwise, just continue
if err := session.VerifyRequest(r); err == nil {
if session.HasExpired() {
session, err = mngr.migrateSession(session, r)
if err != nil {
- return nil, errors.Wrap(err, "unable to migrate to a new session")
+ session = nil
+ sessionErr = errors.Wrap(err, "unable to migrate to a new session")
}
}
} else {
- return nil, errors.Wrap(err, "invalid session")
+ session = nil
+ sessionErr = errors.Wrap(err, "invalid session")
}
}
} else if err != http.ErrNoCookie {
- // The session cookie exists but seems to be invalid, so return an error
- return nil, errors.Wrap(err, "unable to get the session ID from the client")
+ // The session cookie exists but seems to be invalid, so set an error
+ session = nil
+ sessionErr = errors.Wrap(err, "unable to get the session ID from the client")
}
if session == nil {
- // No session found for the client, so create a new one
- session, err = mngr.createSession(r)
- if err != nil {
- return nil, errors.Wrap(err, "unable to assign a new session to the client")
- }
+ // No session found for the client, so create a new one; this will always succeed
+ session = mngr.createSession(r)
}
// Store the session ID on the client side
session.Save(w)
- return session, nil
+ return session, sessionErr
}
-func (mngr *SessionManager) createSession(r *http.Request) (*Session, error) {
- session, err := NewSession(mngr.sessionName, time.Duration(mngr.conf.Webserver.SessionTimeout)*time.Second, r)
- if err != nil {
- return nil, errors.Wrap(err, "unable to create a new session")
- }
+func (mngr *SessionManager) createSession(r *http.Request) *Session {
+ session := NewSession(mngr.sessionName, time.Duration(mngr.conf.Webserver.SessionTimeout)*time.Second, r)
mngr.sessions[session.ID] = session
- return session, nil
+ return session
}
func (mngr *SessionManager) findSession(id string) *Session {
@@ -115,10 +113,7 @@ func (mngr *SessionManager) findSession(id string) *Session {
}
func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (*Session, error) {
- sessionNew, err := mngr.createSession(r)
- if err != nil {
- return nil, err
- }
+ sessionNew := mngr.createSession(r)
// Carry over the old session data, thus preserving the existing session
sessionNew.Data = session.Data
diff --git a/pkg/siteacc/manager.go b/pkg/siteacc/manager/accmanager.go
similarity index 75%
rename from pkg/siteacc/manager.go
rename to pkg/siteacc/manager/accmanager.go
index a7d73336f0..442033c50b 100644
--- a/pkg/siteacc/manager.go
+++ b/pkg/siteacc/manager/accmanager.go
@@ -16,16 +16,13 @@
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
-package siteacc
+package manager
import (
- "net/http"
"strings"
"sync"
"time"
- accpanel "github.com/cs3org/reva/pkg/siteacc/account"
- "github.com/cs3org/reva/pkg/siteacc/admin"
"github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/data"
"github.com/cs3org/reva/pkg/siteacc/email"
@@ -46,23 +43,20 @@ const (
FindBySiteID = "siteid"
)
-// Manager is responsible for all site account related tasks.
-type Manager struct {
+// AccountsManager is responsible for all site account related tasks.
+type AccountsManager struct {
conf *config.Configuration
log *zerolog.Logger
accounts data.Accounts
storage data.Storage
- adminPanel *admin.Panel
- accountPanel *accpanel.Panel
-
smtp *smtpclient.SMTPCredentials
mutex sync.RWMutex
}
-func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger) error {
+func (mngr *AccountsManager) initialize(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return errors.Errorf("no configuration provided")
}
@@ -83,20 +77,6 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return errors.Wrap(err, "unable to create accounts storage")
}
- // Create the admin panel
- if pnl, err := admin.NewPanel(conf, log); err == nil {
- mngr.adminPanel = pnl
- } else {
- return errors.Wrap(err, "unable to create the administration panel")
- }
-
- // Create the account panel
- if pnl, err := accpanel.NewPanel(conf, log); err == nil {
- mngr.accountPanel = pnl
- } else {
- return errors.Wrap(err, "unable to create the account panel")
- }
-
// Create the SMTP client
if conf.SMTP != nil {
mngr.smtp = smtpclient.NewSMTPCredentials(conf.SMTP)
@@ -105,7 +85,7 @@ func (mngr *Manager) initialize(conf *config.Configuration, log *zerolog.Logger)
return nil
}
-func (mngr *Manager) createStorage(driver string) (data.Storage, error) {
+func (mngr *AccountsManager) createStorage(driver string) (data.Storage, error) {
if driver == "file" {
return data.NewFileStorage(mngr.conf, mngr.log)
}
@@ -113,7 +93,7 @@ func (mngr *Manager) createStorage(driver string) (data.Storage, error) {
return nil, errors.Errorf("unknown storage driver %v", driver)
}
-func (mngr *Manager) readAllAccounts() {
+func (mngr *AccountsManager) readAllAccounts() {
if accounts, err := mngr.storage.ReadAll(); err == nil {
mngr.accounts = *accounts
} else {
@@ -122,14 +102,14 @@ func (mngr *Manager) readAllAccounts() {
}
}
-func (mngr *Manager) writeAllAccounts() {
+func (mngr *AccountsManager) writeAllAccounts() {
if err := mngr.storage.WriteAll(&mngr.accounts); err != nil {
// Just warn when not being able to write accounts
mngr.log.Warn().Err(err).Msg("error while writing accounts")
}
}
-func (mngr *Manager) findAccount(by string, value string) (*data.Account, error) {
+func (mngr *AccountsManager) findAccount(by string, value string) (*data.Account, error) {
if len(value) == 0 {
return nil, errors.Errorf("no search value specified")
}
@@ -156,7 +136,7 @@ func (mngr *Manager) findAccount(by string, value string) (*data.Account, error)
return nil, errors.Errorf("no user found matching the specified criteria")
}
-func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool) *data.Account {
+func (mngr *AccountsManager) findAccountByPredicate(predicate func(*data.Account) bool) *data.Account {
for _, account := range mngr.accounts {
if predicate(account) {
return account
@@ -165,20 +145,8 @@ func (mngr *Manager) findAccountByPredicate(predicate func(*data.Account) bool)
return nil
}
-// ShowAdministrationPanel writes the administration panel HTTP output directly to the response writer.
-func (mngr *Manager) ShowAdministrationPanel(w http.ResponseWriter, r *http.Request) error {
- // The adminPanel only shows the stored accounts and offers actions through links, so let it use cloned data
- accounts := mngr.CloneAccounts(true)
- return mngr.adminPanel.Execute(w, r, &accounts)
-}
-
-// ShowAccountPanel writes the account panel HTTP output directly to the response writer.
-func (mngr *Manager) ShowAccountPanel(w http.ResponseWriter, r *http.Request) error {
- return mngr.accountPanel.Execute(w, r)
-}
-
// CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
-func (mngr *Manager) CreateAccount(accountData *data.Account) error {
+func (mngr *AccountsManager) CreateAccount(accountData *data.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -201,7 +169,7 @@ func (mngr *Manager) CreateAccount(accountData *data.Account) error {
}
// UpdateAccount updates the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) error {
+func (mngr *AccountsManager) UpdateAccount(accountData *data.Account, copyData bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -223,7 +191,7 @@ func (mngr *Manager) UpdateAccount(accountData *data.Account, copyData bool) err
}
// FindAccount is used to find an account by various criteria.
-func (mngr *Manager) FindAccount(by string, value string) (*data.Account, error) {
+func (mngr *AccountsManager) FindAccount(by string, value string) (*data.Account, error) {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -238,7 +206,7 @@ func (mngr *Manager) FindAccount(by string, value string) (*data.Account, error)
}
// AuthorizeAccount sets the authorization status of the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AuthorizeAccount(accountData *data.Account, authorized bool) error {
+func (mngr *AccountsManager) AuthorizeAccount(accountData *data.Account, authorized bool) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -261,7 +229,7 @@ func (mngr *Manager) AuthorizeAccount(accountData *data.Account, authorized bool
}
// AssignAPIKeyToAccount is used to assign a new API key to the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int) error {
+func (mngr *AccountsManager) AssignAPIKeyToAccount(accountData *data.Account, flags int) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -298,7 +266,7 @@ func (mngr *Manager) AssignAPIKeyToAccount(accountData *data.Account, flags int)
}
// UnregisterAccountSite unregisters the site associated with the given account.
-func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
+func (mngr *AccountsManager) UnregisterAccountSite(accountData *data.Account) error {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -321,7 +289,7 @@ func (mngr *Manager) UnregisterAccountSite(accountData *data.Account) error {
}
// RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned.
-func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
+func (mngr *AccountsManager) RemoveAccount(accountData *data.Account) error {
mngr.mutex.Lock()
defer mngr.mutex.Unlock()
@@ -338,7 +306,7 @@ func (mngr *Manager) RemoveAccount(accountData *data.Account) error {
}
// CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
-func (mngr *Manager) CloneAccounts(erasePasswords bool) data.Accounts {
+func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts {
mngr.mutex.RLock()
defer mngr.mutex.RUnlock()
@@ -350,23 +318,9 @@ func (mngr *Manager) CloneAccounts(erasePasswords bool) data.Accounts {
return clones
}
-// AuthenticateUser tries to authenticate a given username/password pair. On success, the corresponding user account is returned.
-func (mngr *Manager) AuthenticateUser(name, password string) (*data.Account, error) {
- account, err := mngr.findAccount(FindByEmail, name)
- if err != nil {
- return nil, errors.Wrap(err, "no account with the specified email exists")
- }
-
- // Verify the provided password
- if !account.Password.Compare(password) {
- return nil, errors.Errorf("invalid password")
- }
-
- return account, nil
-}
-
-func newManager(conf *config.Configuration, log *zerolog.Logger) (*Manager, error) {
- mngr := &Manager{}
+// NewAccountsManager creates a new accounts manager instance.
+func NewAccountsManager(conf *config.Configuration, log *zerolog.Logger) (*AccountsManager, error) {
+ mngr := &AccountsManager{}
if err := mngr.initialize(conf, log); err != nil {
return nil, errors.Wrapf(err, "unable to initialize the accounts manager")
}
diff --git a/pkg/siteacc/manager/usersmanager.go b/pkg/siteacc/manager/usersmanager.go
new file mode 100644
index 0000000000..04fbe16d7f
--- /dev/null
+++ b/pkg/siteacc/manager/usersmanager.go
@@ -0,0 +1,83 @@
+// 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 manager
+
+import (
+ "github.com/cs3org/reva/pkg/siteacc/config"
+ "github.com/cs3org/reva/pkg/siteacc/html"
+ "github.com/pkg/errors"
+ "github.com/rs/zerolog"
+)
+
+// UsersManager is responsible for managing logged in users through session objects.
+type UsersManager struct {
+ conf *config.Configuration
+ log *zerolog.Logger
+
+ accountsManager *AccountsManager
+}
+
+func (mngr *UsersManager) initialize(conf *config.Configuration, log *zerolog.Logger, accountsManager *AccountsManager) error {
+ if conf == nil {
+ return errors.Errorf("no configuration provided")
+ }
+ mngr.conf = conf
+
+ if log == nil {
+ return errors.Errorf("no logger provided")
+ }
+ mngr.log = log
+
+ if accountsManager == nil {
+ return errors.Errorf("no accounts manager provided")
+ }
+ mngr.accountsManager = accountsManager
+
+ return nil
+}
+
+// LoginUser tries to login a given username/password pair. On success, the corresponding user account is stored in the session.
+func (mngr *UsersManager) LoginUser(name, password string, session *html.Session) error {
+ account, err := mngr.accountsManager.FindAccount(FindByEmail, name)
+ if err != nil {
+ return errors.Wrap(err, "no account with the specified email exists")
+ }
+
+ // Verify the provided password
+ if !account.Password.Compare(password) {
+ return errors.Errorf("invalid password")
+ }
+
+ return nil
+}
+
+// LogoutUser logs the current user out.
+func (mngr *UsersManager) LogoutUser(session *html.Session) {
+ // Just unset the user account stored in the session
+ session.LoggedInUser = nil
+}
+
+// NewUsersManager creates a new users manager instance.
+func NewUsersManager(conf *config.Configuration, log *zerolog.Logger, accountsManager *AccountsManager) (*UsersManager, error) {
+ mngr := &UsersManager{}
+ if err := mngr.initialize(conf, log, accountsManager); err != nil {
+ return nil, errors.Wrapf(err, "unable to initialize the users manager")
+ }
+ return mngr, nil
+}
diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go
index c0223f673e..0ed120ef0c 100644
--- a/pkg/siteacc/siteacc.go
+++ b/pkg/siteacc/siteacc.go
@@ -22,7 +22,11 @@ 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/config"
+ "github.com/cs3org/reva/pkg/siteacc/html"
+ "github.com/cs3org/reva/pkg/siteacc/manager"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
@@ -32,7 +36,13 @@ type SiteAccounts struct {
conf *config.Configuration
log *zerolog.Logger
- manager *Manager
+ sessions *html.SessionManager
+
+ accountsManager *manager.AccountsManager
+ usersManager *manager.UsersManager
+
+ adminPanel *admin.Panel
+ accountPanel *accpanel.Panel
}
func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog.Logger) error {
@@ -46,12 +56,40 @@ func (siteacc *SiteAccounts) initialize(conf *config.Configuration, log *zerolog
}
siteacc.log = log
+ // Create the session mananger
+ sessions, err := html.NewSessionManager("siteacc_session", conf, log)
+ if err != nil {
+ return errors.Wrap(err, "error while creating the session manager")
+ }
+ siteacc.sessions = sessions
+
// Create the accounts manager instance
- mngr, err := newManager(conf, log)
+ amngr, err := manager.NewAccountsManager(conf, log)
if err != nil {
- return errors.Wrap(err, "error creating the site accounts manager")
+ return errors.Wrap(err, "error creating the accounts manager")
+ }
+ siteacc.accountsManager = amngr
+
+ // Create the users manager instance
+ umngr, err := manager.NewUsersManager(conf, log, siteacc.accountsManager)
+ if err != nil {
+ return errors.Wrap(err, "error creating the users manager")
+ }
+ siteacc.usersManager = umngr
+
+ // Create the admin panel
+ if pnl, err := admin.NewPanel(conf, log); err == nil {
+ siteacc.adminPanel = pnl
+ } else {
+ return errors.Wrap(err, "unable to create the administration panel")
+ }
+
+ // Create the account panel
+ if pnl, err := accpanel.NewPanel(conf, log); err == nil {
+ siteacc.accountPanel = pnl
+ } else {
+ return errors.Wrap(err, "unable to create the account panel")
}
- siteacc.manager = mngr
return nil
}
@@ -61,10 +99,16 @@ func (siteacc *SiteAccounts) RequestHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
+ // Get the active session for the request (or create a new one); a valid session object will always be returned
+ session, err := siteacc.sessions.HandleRequest(w, r)
+ if err != nil {
+ siteacc.log.Err(err).Msg("an error occurred while handling sessions")
+ }
+
epHandled := false
for _, ep := range getEndpoints() {
if ep.Path == r.URL.Path {
- ep.Handler(siteacc.manager, ep, w, r)
+ ep.Handler(siteacc, ep, w, r, session)
epHandled = true
break
}
@@ -77,6 +121,28 @@ func (siteacc *SiteAccounts) RequestHandler() http.Handler {
})
}
+// ShowAdministrationPanel writes the administration panel HTTP output directly to the response writer.
+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)
+}
+
+// ShowAccountPanel writes the account panel HTTP output directly to the response writer.
+func (siteacc *SiteAccounts) ShowAccountPanel(w http.ResponseWriter, r *http.Request, session *html.Session) error {
+ return siteacc.accountPanel.Execute(w, r, session)
+}
+
+// AccountsManager returns the central accounts manager instance.
+func (siteacc *SiteAccounts) AccountsManager() *manager.AccountsManager {
+ return siteacc.accountsManager
+}
+
+// UsersManager returns the central users manager instance.
+func (siteacc *SiteAccounts) UsersManager() *manager.UsersManager {
+ return siteacc.usersManager
+}
+
func (siteacc *SiteAccounts) GetPublicEndpoints() []string {
// TODO: REMOVE!
return []string{"/"}
From 5f556a3c9b7a6c6c19ba16fde7ef9b710a80e221 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Thu, 8 Jul 2021 16:00:46 +0200
Subject: [PATCH 16/60] Implement user login
---
pkg/siteacc/account/login/login.go | 4 +-
pkg/siteacc/account/login/template.go | 3 +-
pkg/siteacc/account/manage/manage.go | 50 +++++++++++++++++
pkg/siteacc/account/manage/template.go | 43 ++++++++++++++
pkg/siteacc/account/panel.go | 56 +++++++++++++++----
.../account/registration/registration.go | 4 +-
pkg/siteacc/admin/panel.go | 4 +-
pkg/siteacc/html/panel.go | 6 +-
pkg/siteacc/html/provider.go | 9 ++-
pkg/siteacc/html/sessionmanager.go | 3 +-
pkg/siteacc/manager/usersmanager.go | 2 +
11 files changed, 162 insertions(+), 22 deletions(-)
create mode 100644 pkg/siteacc/account/manage/manage.go
create mode 100644 pkg/siteacc/account/manage/template.go
diff --git a/pkg/siteacc/account/login/login.go b/pkg/siteacc/account/login/login.go
index 237ff5dcb0..7a31072124 100644
--- a/pkg/siteacc/account/login/login.go
+++ b/pkg/siteacc/account/login/login.go
@@ -24,12 +24,12 @@ type PanelTemplate struct {
html.ContentProvider
}
-// GetTitle returns the title of the htmlPanel.
+// GetTitle returns the title of the panel.
func (template *PanelTemplate) GetTitle() string {
return "ScienceMesh Account Login"
}
-// GetCaption returns the caption which is displayed on the htmlPanel.
+// GetCaption returns the caption which is displayed on the panel.
func (template *PanelTemplate) GetCaption() string {
return "Login to your ScienceMesh Account!"
}
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index d8823cbe0a..0ca5b6916e 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -48,7 +48,8 @@ function handleAction(action) {
xhr.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE) {
if (this.status == 200) {
- setState(STATE_SUCCESS, "Your login was successful!");
+ setState(STATE_SUCCESS, "Your login was successful! Redirecting...");
+ window.location.replace("?path=manage");
} else {
var resp = JSON.parse(this.responseText);
setState(STATE_ERROR, "An error occurred while trying to login your account:" + resp.error + " ", "form", null, true);
diff --git a/pkg/siteacc/account/manage/manage.go b/pkg/siteacc/account/manage/manage.go
new file mode 100644
index 0000000000..f6c3dd23e3
--- /dev/null
+++ b/pkg/siteacc/account/manage/manage.go
@@ -0,0 +1,50 @@
+// 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"
+
+type PanelTemplate struct {
+ html.ContentProvider
+}
+
+// GetTitle returns the title of the panel.
+func (template *PanelTemplate) GetTitle() string {
+ return "ScienceMesh Account Management"
+}
+
+// GetCaption returns the caption which is displayed on the panel.
+func (template *PanelTemplate) GetCaption() string {
+ return "Welcome to your ScienceMesh Account!"
+}
+
+// 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/account/manage/template.go b/pkg/siteacc/account/manage/template.go
new file mode 100644
index 0000000000..a74a2be2f0
--- /dev/null
+++ b/pkg/siteacc/account/manage/template.go
@@ -0,0 +1,43 @@
+// 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 = `
+`
+
+const tplStyleSheet = `
+html * {
+ font-family: arial !important;
+}
+
+.mandatory {
+ color: red;
+ font-weight: bold;
+}
+`
+
+const tplBody = `
+
+
Welcome {{.Account.FirstName}} {{.Account.LastName}}! Here you can manage your ScienceMesh Account.
+
+
+
+ More to come...
+
+`
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index 494f9d9924..062e404861 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -20,10 +20,13 @@ package account
import (
"net/http"
+ "net/url"
"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/config"
+ "github.com/cs3org/reva/pkg/siteacc/data"
"github.com/cs3org/reva/pkg/siteacc/html"
"github.com/pkg/errors"
"github.com/rs/zerolog"
@@ -38,6 +41,7 @@ type Panel struct {
const (
templateLogin = "login"
+ templateManage = "manage"
templateRegistration = "register"
)
@@ -54,6 +58,10 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
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(templateRegistration, ®istration.PanelTemplate{}); err != nil {
return errors.Wrap(err, "unable to create the registration template")
}
@@ -63,39 +71,63 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
// GetActiveTemplate returns the name of the active template.
func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string {
- template := templateLogin // TODO: Check if user is logged in
+ template := templateLogin
- // Invalid paths are just ignored and redirected to the login/main page
+ // Invalid paths are just ignored and redirected to the login page
switch path {
- case templateLogin:
- case templateRegistration:
+ case templateLogin,
+ templateManage,
+ templateRegistration:
template = path
}
- // TODO: Check path access
- // TODO: If user is logged in and path == login, redirect to main
-
return template
}
// PreExecute is called before the actual template is being executed.
-func (panel *Panel) PreExecute(*html.Session, string, *http.Request) error {
- return nil
+func (panel *Panel) PreExecute(session *html.Session, path string, w http.ResponseWriter, r *http.Request) (html.ExecutionResult, error) {
+ protectedPaths := []string{templateManage}
+
+ if session.LoggedInUser == nil {
+ // 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
+ }
+ }
+ } else {
+ // If a user is logged in and tries to login or register again, redirect to the main account page
+ if path == templateLogin || path == templateRegistration {
+ return panel.redirect(templateManage, 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{} {
type TemplateData struct {
+ Account *data.Account
}
- return TemplateData{}
+ return TemplateData{
+ Account: session.LoggedInUser,
+ }
}
return panel.htmlPanel.Execute(w, r, session, dataProvider)
}
-func (panel *Panel) executeLogin(session *html.Session, r *http.Request) error {
- return nil
+func (panel *Panel) redirect(path string, w http.ResponseWriter, r *http.Request) html.ExecutionResult {
+ // Modify the original request URI by replacing the path parameter
+ newURI, _ := url.Parse(r.RequestURI)
+ params := newURI.Query()
+ params.Del("path")
+ params.Add("path", path)
+ newURI.RawQuery = params.Encode()
+ http.Redirect(w, r, newURI.String(), http.StatusFound)
+ return html.AbortExecution
}
// NewPanel creates a new account panel.
diff --git a/pkg/siteacc/account/registration/registration.go b/pkg/siteacc/account/registration/registration.go
index f8720a7e7b..fc2d728192 100644
--- a/pkg/siteacc/account/registration/registration.go
+++ b/pkg/siteacc/account/registration/registration.go
@@ -24,12 +24,12 @@ type PanelTemplate struct {
html.ContentProvider
}
-// GetTitle returns the title of the htmlPanel.
+// GetTitle returns the title of the panel.
func (template *PanelTemplate) GetTitle() string {
return "ScienceMesh Account Registration"
}
-// GetCaption returns the caption which is displayed on the htmlPanel.
+// GetCaption returns the caption which is displayed on the panel.
func (template *PanelTemplate) GetCaption() string {
return "Welcome to the ScienceMesh Account Registration!"
}
diff --git a/pkg/siteacc/admin/panel.go b/pkg/siteacc/admin/panel.go
index 1389ccb052..7d274d8b76 100644
--- a/pkg/siteacc/admin/panel.go
+++ b/pkg/siteacc/admin/panel.go
@@ -87,8 +87,8 @@ func (panel *Panel) GetContentBody() string {
}
// PreExecute is called before the actual template is being executed.
-func (panel *Panel) PreExecute(*html.Session, string, *http.Request) error {
- return nil
+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.
diff --git a/pkg/siteacc/html/panel.go b/pkg/siteacc/html/panel.go
index 8b120ed177..e4d8b4b360 100644
--- a/pkg/siteacc/html/panel.go
+++ b/pkg/siteacc/html/panel.go
@@ -128,7 +128,11 @@ func (panel *Panel) Execute(w http.ResponseWriter, r *http.Request, session *Ses
}
// Perform the pre-execution phase in which the panel provider can intercept the actual execution
- if err := panel.provider.PreExecute(session, actTpl, r); err != nil {
+ if state, err := panel.provider.PreExecute(session, actTpl, w, r); err == nil {
+ if state == AbortExecution {
+ return nil
+ }
+ } else {
return errors.Wrapf(err, "pre-execution of template %v failed", tplName)
}
diff --git a/pkg/siteacc/html/provider.go b/pkg/siteacc/html/provider.go
index 0f70ad521d..e3f437229e 100644
--- a/pkg/siteacc/html/provider.go
+++ b/pkg/siteacc/html/provider.go
@@ -22,13 +22,20 @@ import (
"net/http"
)
+const (
+ ContinueExecution = true
+ AbortExecution = false
+)
+
+type ExecutionResult = bool
+
// PanelProvider handles general panel tasks.
type PanelProvider interface {
// GetActiveTemplate returns the name of the active template.
GetActiveTemplate(*Session, string) string
// PreExecute is called before the actual template is being executed.
- PreExecute(*Session, string, *http.Request) error
+ PreExecute(*Session, string, http.ResponseWriter, *http.Request) (ExecutionResult, error)
}
type PanelDataProvider = func(*Session) interface{}
diff --git a/pkg/siteacc/html/sessionmanager.go b/pkg/siteacc/html/sessionmanager.go
index 52b2fed1fa..3bcbc7179f 100644
--- a/pkg/siteacc/html/sessionmanager.go
+++ b/pkg/siteacc/html/sessionmanager.go
@@ -115,7 +115,8 @@ func (mngr *SessionManager) findSession(id string) *Session {
func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (*Session, error) {
sessionNew := mngr.createSession(r)
- // Carry over the old session data, thus preserving the existing session
+ // Carry over the old session information, thus preserving the existing session
+ sessionNew.LoggedInUser = session.LoggedInUser
sessionNew.Data = session.Data
// Delete the old session
diff --git a/pkg/siteacc/manager/usersmanager.go b/pkg/siteacc/manager/usersmanager.go
index 04fbe16d7f..52b679fce1 100644
--- a/pkg/siteacc/manager/usersmanager.go
+++ b/pkg/siteacc/manager/usersmanager.go
@@ -64,6 +64,8 @@ func (mngr *UsersManager) LoginUser(name, password string, session *html.Session
return errors.Errorf("invalid password")
}
+ // Store the user account in the session
+ session.LoggedInUser = account
return nil
}
From b7b87098d71c8f764d68360cb87cd75807fe9c15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Fri, 9 Jul 2021 13:27:40 +0200
Subject: [PATCH 17/60] Work on user management template
---
pkg/siteacc/account/login/template.go | 5 +-
pkg/siteacc/account/manage/template.go | 70 ++++++++++++++++++++++++--
pkg/siteacc/admin/template.go | 2 +-
3 files changed, 69 insertions(+), 8 deletions(-)
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index 0ca5b6916e..8406738098 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -79,6 +79,7 @@ html * {
}
`
+// TODO: Default values raus
const tplBody = `
Login to your ScienceMesh account using the form below.
@@ -87,9 +88,9 @@ const tplBody = `
Reset
- Login
+ Login
diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go
index d58e3ce184..2ec70f137f 100644
--- a/pkg/siteacc/account/registration/template.go
+++ b/pkg/siteacc/account/registration/template.go
@@ -165,7 +165,7 @@ const tplBody = `
Reset
- Register
+ Register
From aaabc72657f7fb59e76e1133252bde93533d5429 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Mon, 26 Jul 2021 13:53:29 +0200
Subject: [PATCH 39/60] Email updates
---
.../config/http/services/siteacc/_index.md | 38 +++++++++++--------
examples/siteacc/siteacc.toml | 15 ++++----
pkg/siteacc/account/edit/template.go | 2 +-
pkg/siteacc/account/login/template.go | 6 +--
pkg/siteacc/account/registration/template.go | 9 +----
pkg/siteacc/config/config.go | 9 ++++-
pkg/siteacc/email/email.go | 38 +++++++++++++------
pkg/siteacc/email/template.go | 29 ++++++++------
pkg/siteacc/manager/accmanager.go | 6 +--
pkg/siteacc/siteacc.go | 4 +-
10 files changed, 92 insertions(+), 64 deletions(-)
diff --git a/docs/content/en/docs/config/http/services/siteacc/_index.md b/docs/content/en/docs/config/http/services/siteacc/_index.md
index 365842c067..17c25e1f80 100644
--- a/docs/content/en/docs/config/http/services/siteacc/_index.md
+++ b/docs/content/en/docs/config/http/services/siteacc/_index.md
@@ -19,28 +19,36 @@ prefix = "/siteacc"
{{< /highlight >}}
{{% /dir %}}
-{{% dir name="enable_registration_form" type="string" default="false" %}}
-If set to true, the service will expose a simple form for account registration.
+## Email settings
+{{% dir name="notifications_mail" type="string" default="" %}}
+An email address where all notifications are sent to.
+{{< highlight toml >}}
+[http.services.siteacc.email]
+notifications_mail = "notify@example.com"
+{{< /highlight >}}
+{{% /dir %}}
+{{% dir name="accounts_address" type="string" default="" %}}
+The URL for the site accounts user panel which will be used in emails.
{{< highlight toml >}}
-[http.services.siteacc]
-enable_registration_form = true
+[http.services.siteacc.email]
+accounts_address = "https://www.sciencemesh.eu/accounts/"
{{< /highlight >}}
{{% /dir %}}
-{{% dir name="notifications_mail" type="string" default="" %}}
-An email address where all notifications are sent to.
+{{% dir name="gocdb_address" type="string" default="" %}}
+The URL for the GOCDB which will be used in emails.
{{< highlight toml >}}
-[http.services.siteacc]
-notifications_mail = "notify@example.com"
+[http.services.siteacc.email]
+gocdb_address = "https://www.sciencemesh.eu/gocdb/"
{{< /highlight >}}
{{% /dir %}}
-## SMTP settings
+### SMTP settings
{{% dir name="sender_mail" type="string" default="" %}}
An email address from which all emails are sent.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
sender_mail = "notify@example.com"
{{< /highlight >}}
{{% /dir %}}
@@ -48,7 +56,7 @@ sender_mail = "notify@example.com"
{{% dir name="sender_login" type="string" default="" %}}
The login name.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
sender_login = "hans"
{{< /highlight >}}
{{% /dir %}}
@@ -56,7 +64,7 @@ sender_login = "hans"
{{% dir name="sender_password" type="string" default="" %}}
The password for the login.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
password = "secret"
{{< /highlight >}}
{{% /dir %}}
@@ -64,7 +72,7 @@ password = "secret"
{{% dir name="smtp_server" type="string" default="" %}}
The SMTP server to use.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
smtp_server = "smtp.example.com"
{{< /highlight >}}
{{% /dir %}}
@@ -72,7 +80,7 @@ smtp_server = "smtp.example.com"
{{% dir name="smtp_port" type="int" default="25" %}}
The SMTP server port to use.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
smtp_port = 25
{{< /highlight >}}
{{% /dir %}}
@@ -80,7 +88,7 @@ smtp_port = 25
{{% dir name="disable_auth" type="bool" default="false" %}}
Whether to disable authentication.
{{< highlight toml >}}
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
disable_auth = true
{{< /highlight >}}
{{% /dir %}}
diff --git a/examples/siteacc/siteacc.toml b/examples/siteacc/siteacc.toml
index 5e4d33031c..e03f5adcbf 100644
--- a/examples/siteacc/siteacc.toml
+++ b/examples/siteacc/siteacc.toml
@@ -1,20 +1,20 @@
[http]
address = "0.0.0.0:9600"
-[http.services.siteacc]
-# If this is set to true, the service will expose a simple form for account registration
-enable_registration_form = true
-# All notification emails are sent to this email
-notifications_mail = "science.mesh@example.com"
-
# Set up the storage driver
[http.services.siteacc.storage]
driver = "file"
[http.services.siteacc.storage.file]
file = "/var/revad/accounts.json"
+# Email related settings
+[http.services.siteacc.email]
+notifications_mail = "science.mesh@example.com"
+accounts_address = "https://sciencemesh-test.uni-muenster.de/api/accounts/"
+gocdb_address = "https://sciencemesh-test.uni-muenster.de/gocdb/"
+
# The SMTP server used for sending emails
-[http.services.siteacc.smtp]
+[http.services.siteacc.email.smtp]
sender_mail = "science.mesh@example.com"
smtp_server = "mail.example.com"
smtp_port = 25
@@ -23,4 +23,3 @@ disable_auth = true
# The webserver section defines various webserver-related settings
[http.services.siteacc.webserver]
session_timeout = 60
-
diff --git a/pkg/siteacc/account/edit/template.go b/pkg/siteacc/account/edit/template.go
index df3b3fceb8..088e9ed9df 100644
--- a/pkg/siteacc/account/edit/template.go
+++ b/pkg/siteacc/account/edit/template.go
@@ -151,7 +151,7 @@ const tplBody = `
Reset
- Save
+ Save
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index a857203e6e..76cc7c569e 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -120,9 +120,9 @@ const tplBody = `
Reset
- Login
+ Login
diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go
index 2ec70f137f..f169aded47 100644
--- a/pkg/siteacc/account/registration/template.go
+++ b/pkg/siteacc/account/registration/template.go
@@ -116,12 +116,7 @@ html * {
const tplBody = `
-
Fill out the form below to register for a ScienceMesh account. A confirmation email will be sent to you shortly after registration.
-
- After inspection by a ScienceMesh administrator, you will also receive an API key which can then be used in the
- ownCloud or
- Nextcloud web application.
-
+
Fill out the form below to register for a ScienceMesh account. A confirmation email will be sent to you shortly after registration.
@@ -165,7 +160,7 @@ const tplBody = `
Reset
- Register
+ Register
diff --git a/pkg/siteacc/config/config.go b/pkg/siteacc/config/config.go
index 6c5c11acdb..38e8016b33 100644
--- a/pkg/siteacc/config/config.go
+++ b/pkg/siteacc/config/config.go
@@ -32,8 +32,13 @@ type Configuration struct {
} `mapstructure:"file"`
} `mapstructure:"storage"`
- SMTP *smtpclient.SMTPCredentials `mapstructure:"smtp"`
- NotificationsMail string `mapstructure:"notifications_mail"`
+ Email struct {
+ SMTP *smtpclient.SMTPCredentials `mapstructure:"smtp"`
+ NotificationsMail string `mapstructure:"notifications_mail"`
+
+ AccountsAddress string `mapstructure:"accounts_address"`
+ GOCDBAddress string `mapstructure:"gocdb_address"`
+ } `mapstructure:"email"`
SiteRegistration struct {
URL string `mapstructure:"url"`
diff --git a/pkg/siteacc/email/email.go b/pkg/siteacc/email/email.go
index 20b6e4bda8..44adf00fb9 100644
--- a/pkg/siteacc/email/email.go
+++ b/pkg/siteacc/email/email.go
@@ -22,37 +22,53 @@ import (
"bytes"
"text/template"
+ "github.com/cs3org/reva/pkg/siteacc/config"
"github.com/cs3org/reva/pkg/siteacc/data"
"github.com/cs3org/reva/pkg/smtpclient"
"github.com/pkg/errors"
)
+type emailData struct {
+ Account *data.Account
+
+ AccountsAddress string
+ GOCDBAddress string
+}
+
// SendFunction is the definition of email send functions.
-type SendFunction = func(*data.Account, []string, *smtpclient.SMTPCredentials) error
+type SendFunction = func(*data.Account, []string, config.Configuration) error
+
+func getEmailData(account *data.Account, conf config.Configuration) *emailData {
+ return &emailData{
+ Account: account,
+ AccountsAddress: conf.Email.AccountsAddress,
+ GOCDBAddress: conf.Email.GOCDBAddress,
+ }
+}
// SendAccountCreated sends an email about account creation.
-func SendAccountCreated(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
- return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, account, smtp)
+func SendAccountCreated(account *data.Account, recipients []string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, getEmailData(account, conf), conf.Email.SMTP)
}
// SendAPIKeyAssigned sends an email about API key assignment.
-func SendAPIKeyAssigned(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
- return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, account, smtp)
+func SendAPIKeyAssigned(account *data.Account, recipients []string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, getEmailData(account, conf), conf.Email.SMTP)
}
// SendAccountAuthorized sends an email about account authorization.
-func SendAccountAuthorized(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
- return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, account, smtp)
+func SendAccountAuthorized(account *data.Account, recipients []string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, getEmailData(account, conf), conf.Email.SMTP)
}
// SendGOCDBAccessGranted sends an email about granted GOCDB access.
-func SendGOCDBAccessGranted(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
- return send(recipients, "ScienceMesh: GOCDB access granted", gocdbAccessGrantedTemplate, account, smtp)
+func SendGOCDBAccessGranted(account *data.Account, recipients []string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: GOCDB access granted", gocdbAccessGrantedTemplate, getEmailData(account, conf), conf.Email.SMTP)
}
// SendPasswordReset sends an email containing the user's new password.
-func SendPasswordReset(account *data.Account, recipients []string, smtp *smtpclient.SMTPCredentials) error {
- return send(recipients, "ScienceMesh: Password reset", passwordResetTemplate, account, smtp)
+func SendPasswordReset(account *data.Account, recipients []string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Password reset", passwordResetTemplate, getEmailData(account, conf), conf.Email.SMTP)
}
func send(recipients []string, subject string, bodyTemplate string, data interface{}, smtp *smtpclient.SMTPCredentials) error {
diff --git a/pkg/siteacc/email/template.go b/pkg/siteacc/email/template.go
index 60d1931a16..bd14f46e57 100644
--- a/pkg/siteacc/email/template.go
+++ b/pkg/siteacc/email/template.go
@@ -19,30 +19,35 @@
package email
const accountCreatedTemplate = `
-Dear {{.FirstName}} {{.LastName}},
+Dear {{.Account.FirstName}} {{.Account.LastName}},
Your ScienceMesh account has been successfully created!
-An administrator will soon create an API key for your account; you will receive a separate email containing the key.
+Log in to your account by visiting the user account panel:
+{{.AccountsAddress}}
+
+Using this panel, you can modify your information, request an API key or access to the GOCDB, and more.
Kind regards,
The ScienceMesh Team
`
const apiKeyAssignedTemplate = `
-Dear {{.FirstName}} {{.LastName}},
+Dear {{.Account.FirstName}} {{.Account.LastName}},
+
+An API key has been created for your account!
-An API key has been created for your account:
-{{.Data.APIKey}}
+To view your new API key, log in to your user account panel:
+{{.AccountsAddress}}
-Keep this key in a safe and secret place!
+Your key will appear on the front page once logged in.
Kind regards,
The ScienceMesh Team
`
const accountAuthorizedTemplate = `
-Dear {{.FirstName}} {{.LastName}},
+Dear {{.Account.FirstName}} {{.Account.LastName}},
Congratulations - your site registration has been authorized!
@@ -51,22 +56,22 @@ The ScienceMesh Team
`
const gocdbAccessGrantedTemplate = `
-Dear {{.FirstName}} {{.LastName}},
+Dear {{.Account.FirstName}} {{.Account.LastName}},
You have been granted access to the ScienceMesh GOCDB instance:
-https://gocdb.sciencemesh.uni-muenster.de
+{{.GOCDBAddress}}
-Simply use your regular ScienceMesh account credentials to log in.
+Simply use your regular ScienceMesh account credentials to log in to the GOCDB.
Kind regards,
The ScienceMesh Team
`
const passwordResetTemplate = `
-Dear {{.FirstName}} {{.LastName}},
+Dear {{.Account.FirstName}} {{.Account.LastName}},
Your password has been successfully reset!
-Your new password is: {{.Password.Value}}
+Your new password is: {{.Account.Password.Value}}
We recommend to change this password immediately after logging in.
diff --git a/pkg/siteacc/manager/accmanager.go b/pkg/siteacc/manager/accmanager.go
index 8d6a55d86f..a7d0d36655 100644
--- a/pkg/siteacc/manager/accmanager.go
+++ b/pkg/siteacc/manager/accmanager.go
@@ -78,8 +78,8 @@ func (mngr *AccountsManager) initialize(conf *config.Configuration, log *zerolog
}
// Create the SMTP client
- if conf.SMTP != nil {
- mngr.smtp = smtpclient.NewSMTPCredentials(conf.SMTP)
+ if conf.Email.SMTP != nil {
+ mngr.smtp = smtpclient.NewSMTPCredentials(conf.Email.SMTP)
}
return nil
@@ -366,7 +366,7 @@ func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts {
}
func (mngr *AccountsManager) sendEmail(account *data.Account, sendFunc email.SendFunction) {
- _ = sendFunc(account, []string{account.Email, mngr.conf.NotificationsMail}, mngr.smtp)
+ _ = sendFunc(account, []string{account.Email, mngr.conf.Email.NotificationsMail}, *mngr.conf)
}
// NewAccountsManager creates a new accounts manager instance.
diff --git a/pkg/siteacc/siteacc.go b/pkg/siteacc/siteacc.go
index 6d9375ed4c..3640833464 100644
--- a/pkg/siteacc/siteacc.go
+++ b/pkg/siteacc/siteacc.go
@@ -145,8 +145,8 @@ func (siteacc *SiteAccounts) UsersManager() *manager.UsersManager {
}
func (siteacc *SiteAccounts) GetPublicEndpoints() []string {
- // TODO: REMOVE!
- return []string{"/"}
+ // TODO: Only for local testing!
+ // return []string{"/"}
endpoints := make([]string, 0, 5)
for _, ep := range getEndpoints() {
From 2a4467fdbf09f1007994040c505390395b2206c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Tue, 27 Jul 2021 15:42:15 +0200
Subject: [PATCH 40/60] Add request form
---
pkg/siteacc/account/contact/contact.go | 50 +++++++++++
pkg/siteacc/account/contact/template.go | 113 ++++++++++++++++++++++++
pkg/siteacc/account/manage/template.go | 12 +--
pkg/siteacc/account/panel.go | 18 +++-
pkg/siteacc/config/endpoints.go | 2 +
pkg/siteacc/email/email.go | 32 ++++---
pkg/siteacc/email/template.go | 9 ++
pkg/siteacc/endpoints.go | 20 +++++
pkg/siteacc/manager/accmanager.go | 19 ++--
9 files changed, 249 insertions(+), 26 deletions(-)
create mode 100644 pkg/siteacc/account/contact/contact.go
create mode 100644 pkg/siteacc/account/contact/template.go
diff --git a/pkg/siteacc/account/contact/contact.go b/pkg/siteacc/account/contact/contact.go
new file mode 100644
index 0000000000..cdcf0fec69
--- /dev/null
+++ b/pkg/siteacc/account/contact/contact.go
@@ -0,0 +1,50 @@
+// 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 contact
+
+import "github.com/cs3org/reva/pkg/siteacc/html"
+
+type PanelTemplate struct {
+ html.ContentProvider
+}
+
+// GetTitle returns the title of the panel.
+func (template *PanelTemplate) GetTitle() string {
+ return "ScienceMesh Contact Form"
+}
+
+// GetCaption returns the caption which is displayed on the panel.
+func (template *PanelTemplate) GetCaption() string {
+ return "Contact the ScienceMesh administration"
+}
+
+// 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/account/contact/template.go b/pkg/siteacc/account/contact/template.go
new file mode 100644
index 0000000000..ba44e4146b
--- /dev/null
+++ b/pkg/siteacc/account/contact/template.go
@@ -0,0 +1,113 @@
+// 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 contact
+
+const tplJavaScript = `
+function verifyForm(formData) {
+ if (formData.get("subject") == "") {
+ setState(STATE_ERROR, "Please enter a subject.", "form", "subject", true);
+ return false;
+ }
+
+ if (formData.get("message") == "") {
+ setState(STATE_ERROR, "Please enter a message.", "form", "message", true);
+ return false;
+ }
+
+ return true;
+}
+
+function handleAction(action) {
+ const formData = new FormData(document.querySelector("form"));
+ if (!verifyForm(formData)) {
+ return;
+ }
+
+ setState(STATE_STATUS, "Sending message... this should only take a moment.", "form", null, false);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", action);
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState === XMLHttpRequest.DONE) {
+ if (this.status == 200) {
+ setState(STATE_SUCCESS, "Your message was successfully sent! A copy of the message has been sent to your email address.");
+ } else {
+ var resp = JSON.parse(this.responseText);
+ setState(STATE_ERROR, "An error occurred while trying to send your message:" + resp.error + " ", "form", null, true);
+ }
+ }
+ }
+
+ var postData = {
+ "subject": formData.get("subject"),
+ "message": formData.get("message")
+ };
+
+ xhr.send(JSON.stringify(postData));
+}
+`
+
+const tplStyleSheet = `
+html * {
+ font-family: arial !important;
+}
+
+.mandatory {
+ color: red;
+ font-weight: bold;
+}
+`
+
+const tplBody = `
+
+
Contact the ScienceMesh administration using the form below.
+
Please include as much information as possible in your request, especially:
+
+ The site your request refers to (if not obvious from your account information)
+ Your role within the ScienceMesh site (e.g., administrator, operational team member, etc.)
+ Any specific reasons for your request
+ Anything else that might help to process your request
+
+
+
+
+
+ Subject: *
+
+
+ Message: *
+
+ {{if .Params.Message}}{{.Params.Message}}{{end}}
+
+
+
+ Fields marked with * are mandatory.
+
+
+ Reset
+ Send
+
+
+
+
+
Go back to the main account page.
+
+`
diff --git a/pkg/siteacc/account/manage/template.go b/pkg/siteacc/account/manage/template.go
index f3d842d2b4..b41056c7ef 100644
--- a/pkg/siteacc/account/manage/template.go
+++ b/pkg/siteacc/account/manage/template.go
@@ -25,11 +25,13 @@ function handleEditAccount() {
}
function handleRequestAccess() {
- setState(STATE_STATUS, "No one has implemented this function yet :(");
+ setState(STATE_STATUS, "Redirecting to the contact form...");
+ window.location.replace("?path=contact&subject=" + encodeURIComponent("Request GOCDB access"));
}
function handleRequestKey() {
- setState(STATE_STATUS, "No one has implemented this function yet :(");
+ setState(STATE_STATUS, "Redirecting to the contact form...");
+ window.location.replace("?path=contact&subject=" + encodeURIComponent("Request API key"));
}
function handleLogout() {
@@ -70,7 +72,7 @@ html * {
const tplBody = `
Hello {{.Account.FirstName}} {{.Account.LastName}},
-
On this page, you can manage your ScienceMesh user account. This includes editing your personal information, requesting access to the GOCDB and more.
+
On this page, you can manage your ScienceMesh user account. This includes editing your personal information, requesting an API key or access to the GOCDB and more.
@@ -96,8 +98,8 @@ const tplBody = `
Edit account
- Request API Key
- Request GOCDB access
+ Request API Key
+ Request GOCDB access
Logout
diff --git a/pkg/siteacc/account/panel.go b/pkg/siteacc/account/panel.go
index 8d1b6579b1..3ffd1eb716 100644
--- a/pkg/siteacc/account/panel.go
+++ b/pkg/siteacc/account/panel.go
@@ -21,7 +21,9 @@ 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"
@@ -44,6 +46,7 @@ const (
templateLogin = "login"
templateManage = "manage"
templateEdit = "edit"
+ templateContact = "contact"
templateRegistration = "register"
)
@@ -68,6 +71,10 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
return errors.Wrap(err, "unable to create the account editing 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")
}
@@ -77,7 +84,7 @@ func (panel *Panel) initialize(conf *config.Configuration, log *zerolog.Logger)
// GetActiveTemplate returns the name of the active template.
func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string {
- validPaths := []string{templateLogin, templateManage, templateEdit, templateRegistration}
+ validPaths := []string{templateLogin, templateManage, templateEdit, templateContact, templateRegistration}
template := templateLogin
// Only allow valid template paths; redirect to the login page otherwise
@@ -93,7 +100,7 @@ func (panel *Panel) GetActiveTemplate(session *html.Session, path string) string
// 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, templateEdit}
+ protectedPaths := []string{templateManage, templateEdit, templateContact}
if session.LoggedInUser == nil {
// If no user is logged in, redirect protected paths to the login page
@@ -115,12 +122,19 @@ func (panel *Panel) PreExecute(session *html.Session, path string, w http.Respon
// 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]
+ }
+
type TemplateData struct {
Account *data.Account
+ Params map[string]string
}
return TemplateData{
Account: session.LoggedInUser,
+ Params: flatValues,
}
}
return panel.htmlPanel.Execute(w, r, session, dataProvider)
diff --git a/pkg/siteacc/config/endpoints.go b/pkg/siteacc/config/endpoints.go
index e96b1e5e3e..feef521aab 100644
--- a/pkg/siteacc/config/endpoints.go
+++ b/pkg/siteacc/config/endpoints.go
@@ -49,6 +49,8 @@ const (
EndpointLogout = "/logout"
// EndpointResetPassword is the endpoint path for resetting user passwords
EndpointResetPassword = "/reset-password"
+ // EndpointContact is the endpoint path for sending contact emails
+ EndpointContact = "/contact"
// EndpointVerifyUserToken is the endpoint path for user token validation.
EndpointVerifyUserToken = "/verify-user-token"
diff --git a/pkg/siteacc/email/email.go b/pkg/siteacc/email/email.go
index 44adf00fb9..c90df187d1 100644
--- a/pkg/siteacc/email/email.go
+++ b/pkg/siteacc/email/email.go
@@ -33,42 +33,50 @@ type emailData struct {
AccountsAddress string
GOCDBAddress string
+
+ Params map[string]string
}
// SendFunction is the definition of email send functions.
-type SendFunction = func(*data.Account, []string, config.Configuration) error
+type SendFunction = func(*data.Account, []string, map[string]string, config.Configuration) error
-func getEmailData(account *data.Account, conf config.Configuration) *emailData {
+func getEmailData(account *data.Account, conf config.Configuration, params map[string]string) *emailData {
return &emailData{
Account: account,
AccountsAddress: conf.Email.AccountsAddress,
GOCDBAddress: conf.Email.GOCDBAddress,
+ Params: params,
}
}
// SendAccountCreated sends an email about account creation.
-func SendAccountCreated(account *data.Account, recipients []string, conf config.Configuration) error {
- return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, getEmailData(account, conf), conf.Email.SMTP)
+func SendAccountCreated(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Site account created", accountCreatedTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
}
// SendAPIKeyAssigned sends an email about API key assignment.
-func SendAPIKeyAssigned(account *data.Account, recipients []string, conf config.Configuration) error {
- return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, getEmailData(account, conf), conf.Email.SMTP)
+func SendAPIKeyAssigned(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Your API key", apiKeyAssignedTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
}
// SendAccountAuthorized sends an email about account authorization.
-func SendAccountAuthorized(account *data.Account, recipients []string, conf config.Configuration) error {
- return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, getEmailData(account, conf), conf.Email.SMTP)
+func SendAccountAuthorized(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Site registration authorized", accountAuthorizedTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
}
// SendGOCDBAccessGranted sends an email about granted GOCDB access.
-func SendGOCDBAccessGranted(account *data.Account, recipients []string, conf config.Configuration) error {
- return send(recipients, "ScienceMesh: GOCDB access granted", gocdbAccessGrantedTemplate, getEmailData(account, conf), conf.Email.SMTP)
+func SendGOCDBAccessGranted(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: GOCDB access granted", gocdbAccessGrantedTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
}
// SendPasswordReset sends an email containing the user's new password.
-func SendPasswordReset(account *data.Account, recipients []string, conf config.Configuration) error {
- return send(recipients, "ScienceMesh: Password reset", passwordResetTemplate, getEmailData(account, conf), conf.Email.SMTP)
+func SendPasswordReset(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Password reset", passwordResetTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
+}
+
+// SendContactForm sends a generic contact form to the ScienceMesh admins.
+func SendContactForm(account *data.Account, recipients []string, params map[string]string, conf config.Configuration) error {
+ return send(recipients, "ScienceMesh: Contact form", contactFormTemplate, getEmailData(account, conf, params), conf.Email.SMTP)
}
func send(recipients []string, subject string, bodyTemplate string, data interface{}, smtp *smtpclient.SMTPCredentials) error {
diff --git a/pkg/siteacc/email/template.go b/pkg/siteacc/email/template.go
index bd14f46e57..3992a56052 100644
--- a/pkg/siteacc/email/template.go
+++ b/pkg/siteacc/email/template.go
@@ -78,3 +78,12 @@ We recommend to change this password immediately after logging in.
Kind regards,
The ScienceMesh Team
`
+
+const contactFormTemplate = `
+{{.Account.FirstName}} {{.Account.LastName}} ({{.Account.Email}}) has sent the following message:
+
+{{.Params.Subject}}
+---------------------------------------------------------------------------------------------------
+
+{{.Params.Message}}
+`
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 8496ea7052..8d11ae69b4 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -81,6 +81,7 @@ func getEndpoints() []endpoint {
{config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), true},
{config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), true},
{config.EndpointResetPassword, callMethodEndpoint, createMethodCallbacks(nil, handleResetPassword), true},
+ {config.EndpointContact, callMethodEndpoint, createMethodCallbacks(nil, handleContact), true},
// Authentication endpoints
{config.EndpointVerifyUserToken, callMethodEndpoint, createMethodCallbacks(handleVerifyUserToken, nil), true},
// Authorization endpoints
@@ -349,6 +350,25 @@ func handleResetPassword(siteacc *SiteAccounts, values url.Values, body []byte,
return nil, nil
}
+func handleContact(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
+ if session.LoggedInUser == nil {
+ return nil, errors.Errorf("no user is currently logged in")
+ }
+
+ type jsonData struct {
+ Subject string `json:"subject"`
+ Message string `json:"message"`
+ }
+ contactData := &jsonData{}
+ if err := json.Unmarshal(body, contactData); err != nil {
+ return nil, errors.Wrap(err, "invalid form data")
+ }
+
+ // Send an email through the accounts manager
+ siteacc.AccountsManager().SendContactForm(session.LoggedInUser, contactData.Subject, contactData.Message)
+ return nil, nil
+}
+
func handleVerifyUserToken(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
token := values.Get("token")
if token == "" {
diff --git a/pkg/siteacc/manager/accmanager.go b/pkg/siteacc/manager/accmanager.go
index a7d0d36655..b405abafcb 100644
--- a/pkg/siteacc/manager/accmanager.go
+++ b/pkg/siteacc/manager/accmanager.go
@@ -160,7 +160,7 @@ func (mngr *AccountsManager) CreateAccount(accountData *data.Account) error {
mngr.storage.AccountAdded(account)
mngr.writeAllAccounts()
- mngr.sendEmail(account, email.SendAccountCreated)
+ mngr.sendEmail(account, nil, email.SendAccountCreated)
} else {
return errors.Wrap(err, "error while creating account")
}
@@ -201,7 +201,7 @@ func (mngr *AccountsManager) ResetPassword(name string) error {
err = mngr.UpdateAccount(accountUpd, true, false)
if err == nil {
- mngr.sendEmail(accountUpd, email.SendPasswordReset)
+ mngr.sendEmail(accountUpd, nil, email.SendPasswordReset)
}
return err
@@ -246,7 +246,7 @@ func (mngr *AccountsManager) AuthorizeAccount(accountData *data.Account, authori
mngr.writeAllAccounts()
if account.Data.Authorized && account.Data.Authorized != authorizedOld {
- mngr.sendEmail(account, email.SendAccountAuthorized)
+ mngr.sendEmail(account, nil, email.SendAccountAuthorized)
}
return nil
@@ -269,7 +269,7 @@ func (mngr *AccountsManager) GrantGOCDBAccess(accountData *data.Account, grantAc
mngr.writeAllAccounts()
if account.Data.GOCDBAccess && account.Data.GOCDBAccess != accessOld {
- mngr.sendEmail(account, email.SendGOCDBAccessGranted)
+ mngr.sendEmail(account, nil, email.SendGOCDBAccessGranted)
}
return nil
@@ -307,7 +307,7 @@ func (mngr *AccountsManager) AssignAPIKeyToAccount(accountData *data.Account, fl
mngr.storage.AccountUpdated(account)
mngr.writeAllAccounts()
- mngr.sendEmail(account, email.SendAPIKeyAssigned)
+ mngr.sendEmail(account, nil, email.SendAPIKeyAssigned)
return nil
}
@@ -352,6 +352,11 @@ func (mngr *AccountsManager) RemoveAccount(accountData *data.Account) error {
return errors.Errorf("no account with the specified email exists")
}
+// SendContactForm sends a generic email to the ScienceMesh admins.
+func (mngr *AccountsManager) SendContactForm(account *data.Account, subject, message string) {
+ mngr.sendEmail(account, map[string]string{"Subject": subject, "Message": message}, email.SendContactForm)
+}
+
// CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts {
mngr.mutex.RLock()
@@ -365,8 +370,8 @@ func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts {
return clones
}
-func (mngr *AccountsManager) sendEmail(account *data.Account, sendFunc email.SendFunction) {
- _ = sendFunc(account, []string{account.Email, mngr.conf.Email.NotificationsMail}, *mngr.conf)
+func (mngr *AccountsManager) sendEmail(account *data.Account, params map[string]string, sendFunc email.SendFunction) {
+ _ = sendFunc(account, []string{account.Email, mngr.conf.Email.NotificationsMail}, params, *mngr.conf)
}
// NewAccountsManager creates a new accounts manager instance.
From 9f2d0f6e5affb66ffeb625d51cc2cc99205704c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?=
Date: Wed, 28 Jul 2021 16:07:45 +0200
Subject: [PATCH 41/60] Trim form entries
---
pkg/siteacc/account/contact/template.go | 8 +++----
pkg/siteacc/account/edit/template.go | 20 ++++++++--------
pkg/siteacc/account/login/template.go | 4 ++--
pkg/siteacc/account/registration/template.go | 24 ++++++++++----------
pkg/siteacc/data/account.go | 11 +++++++++
pkg/siteacc/endpoints.go | 3 ++-
pkg/siteacc/html/template.go | 10 ++++++++
pkg/siteacc/siteacc.go | 2 +-
8 files changed, 52 insertions(+), 30 deletions(-)
diff --git a/pkg/siteacc/account/contact/template.go b/pkg/siteacc/account/contact/template.go
index ba44e4146b..5d2b974c7e 100644
--- a/pkg/siteacc/account/contact/template.go
+++ b/pkg/siteacc/account/contact/template.go
@@ -20,12 +20,12 @@ package contact
const tplJavaScript = `
function verifyForm(formData) {
- if (formData.get("subject") == "") {
+ if (formData.getTrimmed("subject") == "") {
setState(STATE_ERROR, "Please enter a subject.", "form", "subject", true);
return false;
}
- if (formData.get("message") == "") {
+ if (formData.getTrimmed("message") == "") {
setState(STATE_ERROR, "Please enter a message.", "form", "message", true);
return false;
}
@@ -57,8 +57,8 @@ function handleAction(action) {
}
var postData = {
- "subject": formData.get("subject"),
- "message": formData.get("message")
+ "subject": formData.getTrimmed("subject"),
+ "message": formData.getTrimmed("message")
};
xhr.send(JSON.stringify(postData));
diff --git a/pkg/siteacc/account/edit/template.go b/pkg/siteacc/account/edit/template.go
index 088e9ed9df..9d771d4b03 100644
--- a/pkg/siteacc/account/edit/template.go
+++ b/pkg/siteacc/account/edit/template.go
@@ -20,22 +20,22 @@ package edit
const tplJavaScript = `
function verifyForm(formData) {
- if (formData.get("fname") == "") {
+ if (formData.getTrimmed("fname") == "") {
setState(STATE_ERROR, "Please specify your first name.", "form", "fname", true);
return false;
}
- if (formData.get("lname") == "") {
+ if (formData.getTrimmed("lname") == "") {
setState(STATE_ERROR, "Please specify your last name.", "form", "lname", true);
return false;
}
- if (formData.get("organization") == "") {
+ if (formData.getTrimmed("organization") == "") {
setState(STATE_ERROR, "Please specify your organization/company.", "form", "organization", true);
return false;
}
- if (formData.get("role") == "") {
+ if (formData.getTrimmed("role") == "") {
setState(STATE_ERROR, "Please specify your role within the organization/company.", "form", "role", true);
return false;
}
@@ -79,12 +79,12 @@ function handleAction(action) {
}
var postData = {
- "firstName": formData.get("fname"),
- "lastName": formData.get("lname"),
- "organization": formData.get("organization"),
- "website": formData.get("website"),
- "role": formData.get("role"),
- "phoneNumber": formData.get("phone"),
+ "firstName": formData.getTrimmed("fname"),
+ "lastName": formData.getTrimmed("lname"),
+ "organization": formData.getTrimmed("organization"),
+ "website": formData.getTrimmed("website"),
+ "role": formData.getTrimmed("role"),
+ "phoneNumber": formData.getTrimmed("phone"),
"password": {
"value": formData.get("password")
}
diff --git a/pkg/siteacc/account/login/template.go b/pkg/siteacc/account/login/template.go
index 76cc7c569e..7a3d56805b 100644
--- a/pkg/siteacc/account/login/template.go
+++ b/pkg/siteacc/account/login/template.go
@@ -20,7 +20,7 @@ package login
const tplJavaScript = `
function verifyForm(formData, requirePassword = true) {
- if (formData.get("email") == "") {
+ if (formData.getTrimmed("email") == "") {
setState(STATE_ERROR, "Please enter your email address.", "form", "email", true);
return false;
}
@@ -60,7 +60,7 @@ function handleAction(action) {
}
var postData = {
- "email": formData.get("email"),
+ "email": formData.getTrimmed("email"),
"password": {
"value": formData.get("password")
}
diff --git a/pkg/siteacc/account/registration/template.go b/pkg/siteacc/account/registration/template.go
index f169aded47..3cbd7c2c54 100644
--- a/pkg/siteacc/account/registration/template.go
+++ b/pkg/siteacc/account/registration/template.go
@@ -20,27 +20,27 @@ package registration
const tplJavaScript = `
function verifyForm(formData) {
- if (formData.get("email") == "") {
+ if (formData.getTrimmed("email") == "") {
setState(STATE_ERROR, "Please specify your email address.", "form", "email", true);
return false;
}
- if (formData.get("fname") == "") {
+ if (formData.getTrimmed("fname") == "") {
setState(STATE_ERROR, "Please specify your first name.", "form", "fname", true);
return false;
}
- if (formData.get("lname") == "") {
+ if (formData.getTrimmed("lname") == "") {
setState(STATE_ERROR, "Please specify your last name.", "form", "lname", true);
return false;
}
- if (formData.get("organization") == "") {
+ if (formData.getTrimmed("organization") == "") {
setState(STATE_ERROR, "Please specify your organization/company.", "form", "organization", true);
return false;
}
- if (formData.get("role") == "") {
+ if (formData.getTrimmed("role") == "") {
setState(STATE_ERROR, "Please specify your role within the organization/company.", "form", "role", true);
return false;
}
@@ -87,13 +87,13 @@ function handleAction(action) {
}
var postData = {
- "email": formData.get("email"),
- "firstName": formData.get("fname"),
- "lastName": formData.get("lname"),
- "organization": formData.get("organization"),
- "website": formData.get("website"),
- "role": formData.get("role"),
- "phoneNumber": formData.get("phone"),
+ "email": formData.getTrimmed("email"),
+ "firstName": formData.getTrimmed("fname"),
+ "lastName": formData.getTrimmed("lname"),
+ "organization": formData.getTrimmed("organization"),
+ "website": formData.getTrimmed("website"),
+ "role": formData.getTrimmed("role"),
+ "phoneNumber": formData.getTrimmed("phone"),
"password": {
"value": formData.get("password")
}
diff --git a/pkg/siteacc/data/account.go b/pkg/siteacc/data/account.go
index 5668d67d74..781369218b 100644
--- a/pkg/siteacc/data/account.go
+++ b/pkg/siteacc/data/account.go
@@ -128,6 +128,17 @@ func (acc *Account) CheckScopeAccess(scope string) bool {
return hasAccess
}
+// Cleanup trims all string entries.
+func (acc *Account) Cleanup() {
+ acc.Email = strings.TrimSpace(acc.Email)
+ acc.FirstName = strings.TrimSpace(acc.FirstName)
+ acc.LastName = strings.TrimSpace(acc.LastName)
+ acc.Organization = strings.TrimSpace(acc.Organization)
+ acc.Website = strings.TrimSpace(acc.Website)
+ acc.Role = strings.TrimSpace(acc.Role)
+ acc.PhoneNumber = strings.TrimSpace(acc.PhoneNumber)
+}
+
func (acc *Account) verify(verifyPassword bool) error {
if acc.Email == "" {
return errors.Errorf("no email address provided")
diff --git a/pkg/siteacc/endpoints.go b/pkg/siteacc/endpoints.go
index 8d11ae69b4..5687b77586 100644
--- a/pkg/siteacc/endpoints.go
+++ b/pkg/siteacc/endpoints.go
@@ -365,7 +365,7 @@ func handleContact(siteacc *SiteAccounts, values url.Values, body []byte, sessio
}
// Send an email through the accounts manager
- siteacc.AccountsManager().SendContactForm(session.LoggedInUser, contactData.Subject, contactData.Message)
+ siteacc.AccountsManager().SendContactForm(session.LoggedInUser, strings.TrimSpace(contactData.Subject), strings.TrimSpace(contactData.Message))
return nil, nil
}
@@ -454,6 +454,7 @@ func unmarshalRequestData(body []byte) (*data.Account, error) {
if err := json.Unmarshal(body, account); err != nil {
return nil, errors.Wrap(err, "invalid account data")
}
+ account.Cleanup()
return account, nil
}
diff --git a/pkg/siteacc/html/template.go b/pkg/siteacc/html/template.go
index c24d530054..b0fa54706d 100644
--- a/pkg/siteacc/html/template.go
+++ b/pkg/siteacc/html/template.go
@@ -81,6 +81,16 @@ const panelTemplate = `
}
}
+ FormData.prototype.getTrimmed = function(id) {
+ var val = this.get(id);
+
+ if (val != null) {
+ return val.trim();
+ } else {
+ return "";
+ }
+ }
+
$(CONTENT_JAVASCRIPT)