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 }