github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/core/api/endpoint/label_endpoint.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 package endpoint 13 14 import ( 15 "database/sql" 16 "encoding/json" 17 "fmt" 18 "io/ioutil" 19 "net/http" 20 "strings" 21 22 "github.com/gorilla/mux" 23 24 "github.com/documize/community/core/api/endpoint/models" 25 "github.com/documize/community/core/api/entity" 26 "github.com/documize/community/core/api/mail" 27 "github.com/documize/community/core/api/request" 28 "github.com/documize/community/core/api/util" 29 "github.com/documize/community/core/log" 30 "github.com/documize/community/core/utility" 31 ) 32 33 // AddFolder creates a new folder. 34 func AddFolder(w http.ResponseWriter, r *http.Request) { 35 method := "AddFolder" 36 p := request.GetPersister(r) 37 38 if !p.Context.Editor { 39 writeForbiddenError(w) 40 return 41 } 42 43 defer utility.Close(r.Body) 44 body, err := ioutil.ReadAll(r.Body) 45 46 if err != nil { 47 writePayloadError(w, method, err) 48 return 49 } 50 51 var folder = entity.Label{} 52 err = json.Unmarshal(body, &folder) 53 54 if len(folder.Name) == 0 { 55 writeJSONMarshalError(w, method, "folder", err) 56 return 57 } 58 59 tx, err := request.Db.Beginx() 60 61 if err != nil { 62 writeTransactionError(w, method, err) 63 return 64 } 65 66 p.Context.Transaction = tx 67 68 id := util.UniqueID() 69 folder.RefID = id 70 folder.OrgID = p.Context.OrgID 71 err = addFolder(p, &folder) 72 73 if err != nil { 74 log.IfErr(tx.Rollback()) 75 writeGeneralSQLError(w, method, err) 76 return 77 } 78 79 log.IfErr(tx.Commit()) 80 81 folder, err = p.GetLabel(id) 82 83 json, err := json.Marshal(folder) 84 85 if err != nil { 86 writeJSONMarshalError(w, method, "folder", err) 87 return 88 } 89 90 writeSuccessBytes(w, json) 91 } 92 93 func addFolder(p request.Persister, label *entity.Label) (err error) { 94 label.Type = entity.FolderTypePrivate 95 label.UserID = p.Context.UserID 96 97 err = p.AddLabel(*label) 98 99 if err != nil { 100 return 101 } 102 103 role := entity.LabelRole{} 104 role.LabelID = label.RefID 105 role.OrgID = label.OrgID 106 role.UserID = p.Context.UserID 107 role.CanEdit = true 108 role.CanView = true 109 refID := util.UniqueID() 110 role.RefID = refID 111 112 err = p.AddLabelRole(role) 113 114 return 115 } 116 117 // GetFolder returns the requested folder. 118 func GetFolder(w http.ResponseWriter, r *http.Request) { 119 method := "GetFolder" 120 p := request.GetPersister(r) 121 122 params := mux.Vars(r) 123 id := params["folderID"] 124 125 if len(id) == 0 { 126 writeMissingDataError(w, method, "folderID") 127 return 128 } 129 130 folder, err := p.GetLabel(id) 131 132 if err != nil && err != sql.ErrNoRows { 133 writeServerError(w, method, err) 134 return 135 } 136 137 if err == sql.ErrNoRows { 138 writeNotFoundError(w, method, id) 139 return 140 } 141 142 json, err := json.Marshal(folder) 143 144 if err != nil { 145 writeJSONMarshalError(w, method, "folder", err) 146 return 147 } 148 149 writeSuccessBytes(w, json) 150 } 151 152 // GetFolders returns the folders the user can see. 153 func GetFolders(w http.ResponseWriter, r *http.Request) { 154 method := "GetFolders" 155 p := request.GetPersister(r) 156 157 folders, err := p.GetLabels() 158 159 if err != nil && err != sql.ErrNoRows { 160 writeServerError(w, method, err) 161 return 162 } 163 164 if len(folders) == 0 { 165 folders = []entity.Label{} 166 } 167 168 json, err := json.Marshal(folders) 169 170 if err != nil { 171 writeJSONMarshalError(w, method, "folder", err) 172 return 173 } 174 175 writeSuccessBytes(w, json) 176 } 177 178 // GetFolderVisibility returns the users that can see the shared folders. 179 func GetFolderVisibility(w http.ResponseWriter, r *http.Request) { 180 method := "GetFolderVisibility" 181 p := request.GetPersister(r) 182 183 folders, err := p.GetFolderVisibility() 184 185 if err != nil && err != sql.ErrNoRows { 186 writeServerError(w, method, err) 187 return 188 } 189 190 json, err := json.Marshal(folders) 191 192 if err != nil { 193 writeJSONMarshalError(w, method, "folder", err) 194 return 195 } 196 197 writeSuccessBytes(w, json) 198 } 199 200 // UpdateFolder processes request to save folder object to the database 201 func UpdateFolder(w http.ResponseWriter, r *http.Request) { 202 method := "UpdateFolder" 203 p := request.GetPersister(r) 204 205 if !p.Context.Editor { 206 writeForbiddenError(w) 207 return 208 } 209 210 params := mux.Vars(r) 211 folderID := params["folderID"] 212 213 if len(folderID) == 0 { 214 writeMissingDataError(w, method, "folderID") 215 return 216 } 217 218 defer utility.Close(r.Body) 219 body, err := ioutil.ReadAll(r.Body) 220 221 if err != nil { 222 writePayloadError(w, method, err) 223 return 224 } 225 226 var folder = entity.Label{} 227 err = json.Unmarshal(body, &folder) 228 229 if len(folder.Name) == 0 { 230 writeJSONMarshalError(w, method, "folder", err) 231 return 232 } 233 234 folder.RefID = folderID 235 236 tx, err := request.Db.Beginx() 237 238 if err != nil { 239 writeTransactionError(w, method, err) 240 return 241 } 242 243 p.Context.Transaction = tx 244 245 err = p.UpdateLabel(folder) 246 247 if err != nil { 248 log.IfErr(tx.Rollback()) 249 writeGeneralSQLError(w, method, err) 250 return 251 } 252 253 log.IfErr(tx.Commit()) 254 255 json, err := json.Marshal(folder) 256 257 if err != nil { 258 writeJSONMarshalError(w, method, "folder", err) 259 return 260 } 261 262 writeSuccessBytes(w, json) 263 } 264 265 // RemoveFolder moves documents to another folder before deleting it 266 func RemoveFolder(w http.ResponseWriter, r *http.Request) { 267 method := "RemoveFolder" 268 p := request.GetPersister(r) 269 270 if !p.Context.Editor { 271 writeForbiddenError(w) 272 return 273 } 274 275 params := mux.Vars(r) 276 id := params["folderID"] 277 move := params["moveToId"] 278 279 if len(id) == 0 { 280 writeMissingDataError(w, method, "folderID") 281 return 282 } 283 284 if len(move) == 0 { 285 writeMissingDataError(w, method, "moveToId") 286 return 287 } 288 289 tx, err := request.Db.Beginx() 290 291 if err != nil { 292 writeTransactionError(w, method, err) 293 return 294 } 295 296 p.Context.Transaction = tx 297 298 _, err = p.DeleteLabel(id) 299 300 if err != nil { 301 log.IfErr(tx.Rollback()) 302 writeServerError(w, method, err) 303 return 304 } 305 306 err = p.MoveDocumentLabel(id, move) 307 308 if err != nil { 309 log.IfErr(tx.Rollback()) 310 writeServerError(w, method, err) 311 return 312 } 313 314 err = p.MoveLabelRoles(id, move) 315 316 if err != nil { 317 log.IfErr(tx.Rollback()) 318 writeServerError(w, method, err) 319 return 320 } 321 322 _, err = p.DeletePinnedSpace(id) 323 324 if err != nil && err != sql.ErrNoRows { 325 log.IfErr(tx.Rollback()) 326 writeServerError(w, method, err) 327 return 328 } 329 330 log.IfErr(tx.Commit()) 331 332 writeSuccessString(w, "{}") 333 } 334 335 // SetFolderPermissions persists specified folder permissions 336 func SetFolderPermissions(w http.ResponseWriter, r *http.Request) { 337 method := "SetFolderPermissions" 338 p := request.GetPersister(r) 339 340 params := mux.Vars(r) 341 id := params["folderID"] 342 343 if len(id) == 0 { 344 writeMissingDataError(w, method, "folderID") 345 return 346 } 347 348 label, err := p.GetLabel(id) 349 350 if err != nil { 351 writeBadRequestError(w, method, "No such folder") 352 return 353 } 354 355 if label.UserID != p.Context.UserID { 356 writeForbiddenError(w) 357 return 358 } 359 360 defer utility.Close(r.Body) 361 body, err := ioutil.ReadAll(r.Body) 362 363 if err != nil { 364 writePayloadError(w, method, err) 365 return 366 } 367 368 var model = models.FolderRolesModel{} 369 err = json.Unmarshal(body, &model) 370 371 tx, err := request.Db.Beginx() 372 373 if err != nil { 374 writeTransactionError(w, method, err) 375 return 376 } 377 378 p.Context.Transaction = tx 379 380 // We compare new permisions to what we had before. 381 // Why? So we can send out folder invitation emails. 382 previousRoles, err := p.GetLabelRoles(id) 383 384 if err != nil { 385 writeGeneralSQLError(w, method, err) 386 return 387 } 388 389 // Store all previous roles as map for easy querying 390 previousRoleUsers := make(map[string]bool) 391 392 for _, v := range previousRoles { 393 previousRoleUsers[v.UserID] = true 394 } 395 396 // Who is sharing this folder? 397 inviter, err := p.GetUser(p.Context.UserID) 398 399 if err != nil { 400 log.IfErr(tx.Rollback()) 401 writeGeneralSQLError(w, method, err) 402 return 403 } 404 405 // Nuke all previous permissions for this folder 406 _, err = p.DeleteLabelRoles(id) 407 408 if err != nil { 409 log.IfErr(tx.Rollback()) 410 writeGeneralSQLError(w, method, err) 411 return 412 } 413 414 me := false 415 hasEveryoneRole := false 416 roleCount := 0 417 418 url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 419 420 for _, role := range model.Roles { 421 role.OrgID = p.Context.OrgID 422 role.LabelID = id 423 424 // Ensure the folder owner always has access! 425 if role.UserID == p.Context.UserID { 426 me = true 427 role.CanView = true 428 role.CanEdit = true 429 } 430 431 if len(role.UserID) == 0 && (role.CanView || role.CanEdit) { 432 hasEveryoneRole = true 433 } 434 435 // Only persist if there is a role! 436 if role.CanView || role.CanEdit { 437 roleID := util.UniqueID() 438 role.RefID = roleID 439 err = p.AddLabelRole(role) 440 roleCount++ 441 log.IfErr(err) 442 443 // We send out folder invitation emails to those users 444 // that have *just* been given permissions. 445 if _, isExisting := previousRoleUsers[role.UserID]; !isExisting { 446 447 // we skip 'everyone' (user id != empty string) 448 if len(role.UserID) > 0 { 449 var existingUser entity.User 450 existingUser, err = p.GetUser(role.UserID) 451 452 if err == nil { 453 go mail.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, label.Name, model.Message) 454 log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, existingUser.Email)) 455 } else { 456 writeServerError(w, method, err) 457 } 458 } 459 } 460 } 461 } 462 463 // Do we need to ensure permissions for folder owner when shared? 464 if !me { 465 role := entity.LabelRole{} 466 role.LabelID = id 467 role.OrgID = p.Context.OrgID 468 role.UserID = p.Context.UserID 469 role.CanEdit = true 470 role.CanView = true 471 roleID := util.UniqueID() 472 role.RefID = roleID 473 err = p.AddLabelRole(role) 474 log.IfErr(err) 475 } 476 477 // Mark up folder type as either public, private or restricted access. 478 if hasEveryoneRole { 479 label.Type = entity.FolderTypePublic 480 } else { 481 if roleCount > 1 { 482 label.Type = entity.FolderTypeRestricted 483 } else { 484 label.Type = entity.FolderTypePrivate 485 } 486 } 487 488 log.Error("p.UpdateLabel()", p.UpdateLabel(label)) 489 490 log.Error("tx.Commit()", tx.Commit()) 491 492 writeSuccessEmptyJSON(w) 493 } 494 495 // GetFolderPermissions returns user permissions for the requested folder. 496 func GetFolderPermissions(w http.ResponseWriter, r *http.Request) { 497 method := "GetFolderPermissions" 498 p := request.GetPersister(r) 499 500 params := mux.Vars(r) 501 folderID := params["folderID"] 502 503 if len(folderID) == 0 { 504 writeMissingDataError(w, method, "folderID") 505 return 506 } 507 508 roles, err := p.GetLabelRoles(folderID) 509 510 if err != nil && err != sql.ErrNoRows { 511 writeGeneralSQLError(w, method, err) 512 return 513 } 514 515 if len(roles) == 0 { 516 roles = []entity.LabelRole{} 517 } 518 519 json, err := json.Marshal(roles) 520 521 if err != nil { 522 writeJSONMarshalError(w, method, "folder-permissions", err) 523 return 524 } 525 526 writeSuccessBytes(w, json) 527 } 528 529 // AcceptSharedFolder records the fact that a user has completed folder onboard process. 530 func AcceptSharedFolder(w http.ResponseWriter, r *http.Request) { 531 method := "AcceptSharedFolder" 532 p := request.GetPersister(r) 533 534 params := mux.Vars(r) 535 folderID := params["folderID"] 536 537 if len(folderID) == 0 { 538 writeMissingDataError(w, method, "folderID") 539 return 540 } 541 542 org, err := p.GetOrganizationByDomain(p.Context.Subdomain) 543 544 if err != nil { 545 writeGeneralSQLError(w, method, err) 546 return 547 } 548 549 p.Context.OrgID = org.RefID 550 551 defer utility.Close(r.Body) 552 body, err := ioutil.ReadAll(r.Body) 553 554 if err != nil { 555 writePayloadError(w, method, err) 556 return 557 } 558 559 var model = models.AcceptSharedFolderModel{} 560 err = json.Unmarshal(body, &model) 561 562 if err != nil { 563 writePayloadError(w, method, err) 564 return 565 } 566 567 if len(model.Serial) == 0 || len(model.Firstname) == 0 || len(model.Lastname) == 0 || len(model.Password) == 0 { 568 writeJSONMarshalError(w, method, "missing field data", err) 569 return 570 } 571 572 if err != nil { 573 writeGeneralSQLError(w, method, err) 574 return 575 } 576 577 user, err := p.GetUserBySerial(model.Serial) 578 579 // User has already on-boarded. 580 if err != nil && err == sql.ErrNoRows { 581 writeDuplicateError(w, method, "user") 582 return 583 } 584 585 if err != nil { 586 writeGeneralSQLError(w, method, err) 587 return 588 } 589 590 user.Firstname = model.Firstname 591 user.Lastname = model.Lastname 592 user.Initials = utility.MakeInitials(user.Firstname, user.Lastname) 593 594 tx, err := request.Db.Beginx() 595 596 if err != nil { 597 writeTransactionError(w, method, err) 598 return 599 } 600 601 p.Context.Transaction = tx 602 603 err = p.UpdateUser(user) 604 605 if err != nil { 606 log.IfErr(tx.Rollback()) 607 writeGeneralSQLError(w, method, err) 608 return 609 } 610 611 salt := util.GenerateSalt() 612 613 log.IfErr(p.UpdateUserPassword(user.RefID, salt, util.GeneratePassword(model.Password, salt))) 614 615 if err != nil { 616 log.IfErr(tx.Rollback()) 617 writeGeneralSQLError(w, method, err) 618 return 619 } 620 621 log.IfErr(tx.Commit()) 622 623 data, err := json.Marshal(user) 624 625 if err != nil { 626 writeJSONMarshalError(w, method, "user", err) 627 return 628 } 629 630 writeSuccessBytes(w, data) 631 } 632 633 // InviteToFolder sends users folder invitation emails. 634 func InviteToFolder(w http.ResponseWriter, r *http.Request) { 635 method := "InviteToFolder" 636 p := request.GetPersister(r) 637 638 params := mux.Vars(r) 639 id := params["folderID"] 640 641 if len(id) == 0 { 642 writeMissingDataError(w, method, "folderID") 643 return 644 } 645 646 label, err := p.GetLabel(id) 647 648 if err != nil { 649 writeBadRequestError(w, method, "folder not found") 650 return 651 } 652 653 if label.UserID != p.Context.UserID { 654 writeForbiddenError(w) 655 return 656 } 657 658 defer utility.Close(r.Body) 659 body, err := ioutil.ReadAll(r.Body) 660 661 if err != nil { 662 writePayloadError(w, method, err) 663 return 664 } 665 666 var model = models.FolderInvitationModel{} 667 err = json.Unmarshal(body, &model) 668 669 tx, err := request.Db.Beginx() 670 671 if err != nil { 672 writeTransactionError(w, method, err) 673 return 674 } 675 676 p.Context.Transaction = tx 677 678 inviter, err := p.GetUser(p.Context.UserID) 679 680 if err != nil { 681 writeGeneralSQLError(w, method, err) 682 return 683 } 684 685 for _, email := range model.Recipients { 686 var user entity.User 687 user, err = p.GetUserByEmail(email) 688 689 if err != nil && err != sql.ErrNoRows { 690 log.IfErr(tx.Rollback()) 691 writeGeneralSQLError(w, method, err) 692 return 693 } 694 695 if len(user.RefID) > 0 { 696 697 // Ensure they have access to this organization 698 accounts, err2 := p.GetUserAccounts(user.RefID) 699 700 if err2 != nil { 701 log.IfErr(tx.Rollback()) 702 writeGeneralSQLError(w, method, err2) 703 return 704 } 705 706 // we create if they c 707 hasAccess := false 708 for _, a := range accounts { 709 if a.OrgID == p.Context.OrgID { 710 hasAccess = true 711 } 712 } 713 714 if !hasAccess { 715 var a entity.Account 716 a.UserID = user.RefID 717 a.OrgID = p.Context.OrgID 718 a.Admin = false 719 a.Editor = false 720 accountID := util.UniqueID() 721 a.RefID = accountID 722 723 err = p.AddAccount(a) 724 725 if err != nil { 726 log.IfErr(tx.Rollback()) 727 writeGeneralSQLError(w, method, err) 728 return 729 } 730 } 731 732 // Ensure they have folder roles 733 _, err = p.DeleteUserFolderRoles(label.RefID, user.RefID) 734 log.IfErr(err) 735 736 role := entity.LabelRole{} 737 role.LabelID = label.RefID 738 role.OrgID = p.Context.OrgID 739 role.UserID = user.RefID 740 role.CanEdit = false 741 role.CanView = true 742 roleID := util.UniqueID() 743 role.RefID = roleID 744 745 err = p.AddLabelRole(role) 746 747 if err != nil { 748 log.IfErr(tx.Rollback()) 749 writeGeneralSQLError(w, method, err) 750 return 751 } 752 753 url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 754 go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, label.Name, model.Message) 755 log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, email)) 756 } else { 757 // On-board new user 758 if strings.Contains(email, "@") { 759 url := p.Context.GetAppURL(fmt.Sprintf("auth/share/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 760 err = inviteNewUserToSharedFolder(p, email, inviter, url, label, model.Message) 761 762 if err != nil { 763 log.IfErr(tx.Rollback()) 764 writeServerError(w, method, err) 765 return 766 } 767 768 log.Info(fmt.Sprintf("%s is sharing space %s with new user %s", inviter.Email, label.Name, email)) 769 } 770 } 771 } 772 773 // We ensure that the folder is marked as restricted as a minimum! 774 if len(model.Recipients) > 0 && label.Type == entity.FolderTypePrivate { 775 label.Type = entity.FolderTypeRestricted 776 err = p.UpdateLabel(label) 777 778 if err != nil { 779 log.IfErr(tx.Rollback()) 780 writeServerError(w, method, err) 781 return 782 } 783 } 784 785 log.IfErr(tx.Commit()) 786 787 _, err = w.Write([]byte("{}")) 788 log.IfErr(err) 789 } 790 791 // Invite new user to a folder that someone has shared with them. 792 // We create the user account with default values and then take them 793 // through a welcome process designed to capture profile data. 794 // We add them to the organization and grant them view-only folder access. 795 func inviteNewUserToSharedFolder(p request.Persister, email string, invitedBy entity.User, 796 baseURL string, label entity.Label, invitationMessage string) (err error) { 797 798 var user = entity.User{} 799 user.Email = email 800 user.Firstname = email 801 user.Lastname = "" 802 user.Salt = util.GenerateSalt() 803 requestedPassword := util.GenerateRandomPassword() 804 user.Password = util.GeneratePassword(requestedPassword, user.Salt) 805 userID := util.UniqueID() 806 user.RefID = userID 807 808 err = p.AddUser(user) 809 810 if err != nil { 811 return 812 } 813 814 // Let's give this user access to the organization 815 var a entity.Account 816 a.UserID = userID 817 a.OrgID = p.Context.OrgID 818 a.Admin = false 819 a.Editor = false 820 accountID := util.UniqueID() 821 a.RefID = accountID 822 823 err = p.AddAccount(a) 824 825 if err != nil { 826 return 827 } 828 829 role := entity.LabelRole{} 830 role.LabelID = label.RefID 831 role.OrgID = p.Context.OrgID 832 role.UserID = userID 833 role.CanEdit = false 834 role.CanView = true 835 roleID := util.UniqueID() 836 role.RefID = roleID 837 838 err = p.AddLabelRole(role) 839 840 if err != nil { 841 return 842 } 843 844 url := fmt.Sprintf("%s/%s", baseURL, user.Salt) 845 go mail.ShareFolderNewUser(user.Email, invitedBy.Fullname(), url, label.Name, invitationMessage) 846 847 return 848 }