github.com/cs3org/reva/v2@v2.27.7/pkg/siteacc/endpoints.go (about)

     1  // Copyright 2018-2020 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package siteacc
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"net/url"
    27  	"strings"
    28  
    29  	"github.com/cs3org/reva/v2/pkg/siteacc/config"
    30  	"github.com/cs3org/reva/v2/pkg/siteacc/data"
    31  	"github.com/cs3org/reva/v2/pkg/siteacc/html"
    32  	"github.com/cs3org/reva/v2/pkg/siteacc/manager"
    33  	"github.com/pkg/errors"
    34  	"github.com/prometheus/alertmanager/template"
    35  )
    36  
    37  const (
    38  	invokerUser = "user"
    39  )
    40  
    41  type methodCallback = func(*SiteAccounts, url.Values, []byte, *html.Session) (interface{}, error)
    42  type accessSetterCallback = func(*manager.AccountsManager, *data.Account, bool) error
    43  
    44  type endpoint struct {
    45  	Path            string
    46  	Handler         func(*SiteAccounts, endpoint, http.ResponseWriter, *http.Request, *html.Session)
    47  	MethodCallbacks map[string]methodCallback
    48  	IsPublic        bool
    49  }
    50  
    51  func createMethodCallbacks(cbGet methodCallback, cbPost methodCallback) map[string]methodCallback {
    52  	callbacks := make(map[string]methodCallback)
    53  
    54  	if cbGet != nil {
    55  		callbacks[http.MethodGet] = cbGet
    56  	}
    57  
    58  	if cbPost != nil {
    59  		callbacks[http.MethodPost] = cbPost
    60  	}
    61  
    62  	return callbacks
    63  }
    64  
    65  func getEndpoints() []endpoint {
    66  	endpoints := []endpoint{
    67  		// Form/panel endpoints
    68  		{config.EndpointAdministration, callAdministrationEndpoint, nil, false},
    69  		{config.EndpointAccount, callAccountEndpoint, nil, true},
    70  		// General account endpoints
    71  		{config.EndpointList, callMethodEndpoint, createMethodCallbacks(handleList, nil), false},
    72  		{config.EndpointFind, callMethodEndpoint, createMethodCallbacks(handleFind, nil), false},
    73  		{config.EndpointCreate, callMethodEndpoint, createMethodCallbacks(nil, handleCreate), true},
    74  		{config.EndpointUpdate, callMethodEndpoint, createMethodCallbacks(nil, handleUpdate), false},
    75  		{config.EndpointConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleConfigure), false},
    76  		{config.EndpointRemove, callMethodEndpoint, createMethodCallbacks(nil, handleRemove), false},
    77  		// Site endpoints
    78  		{config.EndpointSiteGet, callMethodEndpoint, createMethodCallbacks(handleSiteGet, nil), false},
    79  		{config.EndpointSiteConfigure, callMethodEndpoint, createMethodCallbacks(nil, handleSiteConfigure), false},
    80  		// Login endpoints
    81  		{config.EndpointLogin, callMethodEndpoint, createMethodCallbacks(nil, handleLogin), true},
    82  		{config.EndpointLogout, callMethodEndpoint, createMethodCallbacks(handleLogout, nil), true},
    83  		{config.EndpointResetPassword, callMethodEndpoint, createMethodCallbacks(nil, handleResetPassword), true},
    84  		{config.EndpointContact, callMethodEndpoint, createMethodCallbacks(nil, handleContact), true},
    85  		// Authentication endpoints
    86  		{config.EndpointVerifyUserToken, callMethodEndpoint, createMethodCallbacks(handleVerifyUserToken, nil), true},
    87  		// Access management endpoints
    88  		{config.EndpointGrantSiteAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantSiteAccess), false},
    89  		{config.EndpointGrantGOCDBAccess, callMethodEndpoint, createMethodCallbacks(nil, handleGrantGOCDBAccess), false},
    90  		// Alerting endpoints
    91  		{config.EndpointDispatchAlert, callMethodEndpoint, createMethodCallbacks(nil, handleDispatchAlert), false},
    92  	}
    93  
    94  	return endpoints
    95  }
    96  
    97  func callAdministrationEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
    98  	if err := siteacc.ShowAdministrationPanel(w, r, session); err != nil {
    99  		w.WriteHeader(http.StatusInternalServerError)
   100  		_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the administration panel: %v", err)))
   101  	}
   102  }
   103  
   104  func callAccountEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
   105  	if err := siteacc.ShowAccountPanel(w, r, session); err != nil {
   106  		w.WriteHeader(http.StatusInternalServerError)
   107  		_, _ = w.Write([]byte(fmt.Sprintf("Unable to show the account panel: %v", err)))
   108  	}
   109  }
   110  
   111  func callMethodEndpoint(siteacc *SiteAccounts, ep endpoint, w http.ResponseWriter, r *http.Request, session *html.Session) {
   112  	// Every request to the accounts service results in a standardized JSON response
   113  	type Response struct {
   114  		Success bool        `json:"success"`
   115  		Error   string      `json:"error,omitempty"`
   116  		Data    interface{} `json:"data,omitempty"`
   117  	}
   118  
   119  	// The default response is an unknown requestHandler (for the specified method)
   120  	resp := Response{
   121  		Success: false,
   122  		Error:   fmt.Sprintf("unknown endpoint %v for method %v", r.URL.Path, r.Method),
   123  		Data:    nil,
   124  	}
   125  
   126  	if ep.MethodCallbacks != nil {
   127  		// Search for a matching method in the list of callbacks
   128  		for method, cb := range ep.MethodCallbacks {
   129  			if method == r.Method {
   130  				body, _ := io.ReadAll(r.Body)
   131  
   132  				if respData, err := cb(siteacc, r.URL.Query(), body, session); err == nil {
   133  					resp.Success = true
   134  					resp.Error = ""
   135  					resp.Data = respData
   136  				} else {
   137  					resp.Success = false
   138  					resp.Error = fmt.Sprintf("%v", err)
   139  					resp.Data = nil
   140  				}
   141  			}
   142  		}
   143  	}
   144  
   145  	// Any failure during query handling results in a bad request
   146  	if !resp.Success {
   147  		w.WriteHeader(http.StatusBadRequest)
   148  	}
   149  
   150  	// Responses here are always JSON
   151  	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
   152  
   153  	jsonData, _ := json.MarshalIndent(&resp, "", "\t")
   154  	_, _ = w.Write(jsonData)
   155  }
   156  
   157  func handleList(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   158  	return siteacc.AccountsManager().CloneAccounts(true), nil
   159  }
   160  
   161  func handleFind(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   162  	account, err := findAccount(siteacc, values.Get("by"), values.Get("value"))
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	return map[string]interface{}{"account": account.Clone(true)}, nil
   167  }
   168  
   169  func handleCreate(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   170  	account, err := unmarshalRequestData(body)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	// Create a new account through the accounts manager
   176  	if err := siteacc.AccountsManager().CreateAccount(account); err != nil {
   177  		return nil, errors.Wrap(err, "unable to create account")
   178  	}
   179  
   180  	return nil, nil
   181  }
   182  
   183  func handleUpdate(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   184  	account, err := unmarshalRequestData(body)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	email, setPassword, err := processInvoker(siteacc, values, session)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	account.Email = email
   194  
   195  	// Update the account through the accounts manager
   196  	if err := siteacc.AccountsManager().UpdateAccount(account, setPassword, false); err != nil {
   197  		return nil, errors.Wrap(err, "unable to update account")
   198  	}
   199  
   200  	return nil, nil
   201  }
   202  
   203  func handleConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   204  	account, err := unmarshalRequestData(body)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	email, _, err := processInvoker(siteacc, values, session)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	account.Email = email
   214  
   215  	// Configure the account through the accounts manager
   216  	if err := siteacc.AccountsManager().ConfigureAccount(account); err != nil {
   217  		return nil, errors.Wrap(err, "unable to configure account")
   218  	}
   219  
   220  	return nil, nil
   221  }
   222  
   223  func handleRemove(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   224  	account, err := unmarshalRequestData(body)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	// Remove the account through the accounts manager
   230  	if err := siteacc.AccountsManager().RemoveAccount(account); err != nil {
   231  		return nil, errors.Wrap(err, "unable to remove account")
   232  	}
   233  
   234  	return nil, nil
   235  }
   236  
   237  func handleSiteGet(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   238  	siteID := values.Get("site")
   239  	if siteID == "" {
   240  		return nil, errors.Errorf("no site specified")
   241  	}
   242  	site := siteacc.SitesManager().FindSite(siteID)
   243  	if site == nil {
   244  		return nil, errors.Errorf("no site with ID %v exists", siteID)
   245  	}
   246  	return map[string]interface{}{"site": site.Clone(false)}, nil
   247  }
   248  
   249  func handleSiteConfigure(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   250  	email, _, err := processInvoker(siteacc, values, session)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	account, err := siteacc.AccountsManager().FindAccount(manager.FindByEmail, email)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	siteData := &data.Site{}
   260  	if err := json.Unmarshal(body, siteData); err != nil {
   261  		return nil, errors.Wrap(err, "invalid form data")
   262  	}
   263  	siteData.ID = account.Site
   264  
   265  	// Configure the site through the sites manager
   266  	if err := siteacc.SitesManager().UpdateSite(siteData); err != nil {
   267  		return nil, errors.Wrap(err, "unable to configure site")
   268  	}
   269  
   270  	return nil, nil
   271  }
   272  
   273  func handleLogin(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   274  	account, err := unmarshalRequestData(body)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	// Login the user through the users manager
   280  	token, err := siteacc.UsersManager().LoginUser(account.Email, account.Password.Value, values.Get("scope"), session)
   281  	if err != nil {
   282  		return nil, errors.Wrap(err, "unable to login user")
   283  	}
   284  
   285  	return token, nil
   286  }
   287  
   288  func handleLogout(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   289  	// Logout the user through the users manager
   290  	siteacc.UsersManager().LogoutUser(session)
   291  	return nil, nil
   292  }
   293  
   294  func handleResetPassword(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   295  	account, err := unmarshalRequestData(body)
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  
   300  	// Reset the password through the users manager
   301  	if err := siteacc.AccountsManager().ResetPassword(account.Email); err != nil {
   302  		return nil, errors.Wrap(err, "unable to reset password")
   303  	}
   304  
   305  	return nil, nil
   306  }
   307  
   308  func handleContact(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   309  	if !session.IsUserLoggedIn() {
   310  		return nil, errors.Errorf("no user is currently logged in")
   311  	}
   312  
   313  	type jsonData struct {
   314  		Subject string `json:"subject"`
   315  		Message string `json:"message"`
   316  	}
   317  	contactData := &jsonData{}
   318  	if err := json.Unmarshal(body, contactData); err != nil {
   319  		return nil, errors.Wrap(err, "invalid form data")
   320  	}
   321  
   322  	// Send an email through the accounts manager
   323  	siteacc.AccountsManager().SendContactForm(session.LoggedInUser().Account, strings.TrimSpace(contactData.Subject), strings.TrimSpace(contactData.Message))
   324  	return nil, nil
   325  }
   326  
   327  func handleVerifyUserToken(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   328  	token := values.Get("token")
   329  	if token == "" {
   330  		return nil, errors.Errorf("no token specified")
   331  	}
   332  
   333  	user := values.Get("user")
   334  	if user == "" {
   335  		return nil, errors.Errorf("no user specified")
   336  	}
   337  
   338  	// Verify the user token using the users manager
   339  	newToken, err := siteacc.UsersManager().VerifyUserToken(token, user, values.Get("scope"))
   340  	if err != nil {
   341  		return nil, errors.Wrap(err, "token verification failed")
   342  	}
   343  
   344  	return newToken, nil
   345  }
   346  
   347  func handleDispatchAlert(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   348  	alertsData := &template.Data{}
   349  	if err := json.Unmarshal(body, alertsData); err != nil {
   350  		return nil, errors.Wrap(err, "unable to unmarshal the alerts data")
   351  	}
   352  
   353  	// Dispatch the alerts using the alerts dispatcher
   354  	if err := siteacc.AlertsDispatcher().DispatchAlerts(alertsData, siteacc.AccountsManager().CloneAccounts(true)); err != nil {
   355  		return nil, errors.Wrap(err, "error while dispatching the alerts")
   356  	}
   357  
   358  	return nil, nil
   359  }
   360  
   361  func handleGrantSiteAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   362  	return handleGrantAccess((*manager.AccountsManager).GrantSiteAccess, siteacc, values, body, session)
   363  }
   364  
   365  func handleGrantGOCDBAccess(siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   366  	return handleGrantAccess((*manager.AccountsManager).GrantGOCDBAccess, siteacc, values, body, session)
   367  }
   368  
   369  func handleGrantAccess(accessSetter accessSetterCallback, siteacc *SiteAccounts, values url.Values, body []byte, session *html.Session) (interface{}, error) {
   370  	account, err := unmarshalRequestData(body)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  
   375  	if val := values.Get("status"); len(val) > 0 {
   376  		var grantAccess bool
   377  		switch strings.ToLower(val) {
   378  		case "true":
   379  			grantAccess = true
   380  
   381  		case "false":
   382  			grantAccess = false
   383  
   384  		default:
   385  			return nil, errors.Errorf("unsupported access status %v", val[0])
   386  		}
   387  
   388  		// Grant access to the account through the accounts manager
   389  		if err := accessSetter(siteacc.AccountsManager(), account, grantAccess); err != nil {
   390  			return nil, errors.Wrap(err, "unable to change the access status of the account")
   391  		}
   392  	} else {
   393  		return nil, errors.Errorf("no access status provided")
   394  	}
   395  
   396  	return nil, nil
   397  }
   398  
   399  func unmarshalRequestData(body []byte) (*data.Account, error) {
   400  	account := &data.Account{}
   401  	if err := json.Unmarshal(body, account); err != nil {
   402  		return nil, errors.Wrap(err, "invalid account data")
   403  	}
   404  	account.Cleanup()
   405  	return account, nil
   406  }
   407  
   408  func findAccount(siteacc *SiteAccounts, by string, value string) (*data.Account, error) {
   409  	if len(by) == 0 && len(value) == 0 {
   410  		return nil, errors.Errorf("missing search criteria")
   411  	}
   412  
   413  	// Find the account using the accounts manager
   414  	account, err := siteacc.AccountsManager().FindAccount(by, value)
   415  	if err != nil {
   416  		return nil, errors.Wrap(err, "user not found")
   417  	}
   418  	return account, nil
   419  }
   420  
   421  func processInvoker(siteacc *SiteAccounts, values url.Values, session *html.Session) (string, bool, error) {
   422  	var email string
   423  	var invokedByUser bool
   424  
   425  	switch strings.ToLower(values.Get("invoker")) {
   426  	case invokerUser:
   427  		// If this endpoint was called by the user, set the account email from the stored session
   428  		if !session.IsUserLoggedIn() {
   429  			return "", false, errors.Errorf("no user is currently logged in")
   430  		}
   431  
   432  		email = session.LoggedInUser().Account.Email
   433  		invokedByUser = true
   434  
   435  	default:
   436  		return "", false, errors.Errorf("no invoker provided")
   437  	}
   438  
   439  	return email, invokedByUser, nil
   440  }