github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/documize/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/documize/api/endpoint/models" 25 "github.com/documize/community/documize/api/entity" 26 "github.com/documize/community/documize/api/mail" 27 "github.com/documize/community/documize/api/request" 28 "github.com/documize/community/documize/api/util" 29 "github.com/documize/community/wordsmith/log" 30 "github.com/documize/community/wordsmith/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 log.IfErr(tx.Commit()) 323 324 writeSuccessString(w, "{}") 325 } 326 327 // SetFolderPermissions persists specified folder permissions 328 func SetFolderPermissions(w http.ResponseWriter, r *http.Request) { 329 method := "SetFolderPermissions" 330 p := request.GetPersister(r) 331 332 params := mux.Vars(r) 333 id := params["folderID"] 334 335 if len(id) == 0 { 336 writeMissingDataError(w, method, "folderID") 337 return 338 } 339 340 label, err := p.GetLabel(id) 341 342 if err != nil { 343 writeBadRequestError(w, method, "No such folder") 344 return 345 } 346 347 if label.UserID != p.Context.UserID { 348 writeForbiddenError(w) 349 return 350 } 351 352 defer utility.Close(r.Body) 353 body, err := ioutil.ReadAll(r.Body) 354 355 if err != nil { 356 writePayloadError(w, method, err) 357 return 358 } 359 360 var model = models.FolderRolesModel{} 361 err = json.Unmarshal(body, &model) 362 363 tx, err := request.Db.Beginx() 364 365 if err != nil { 366 writeTransactionError(w, method, err) 367 return 368 } 369 370 p.Context.Transaction = tx 371 372 // We compare new permisions to what we had before. 373 // Why? So we can send out folder invitation emails. 374 previousRoles, err := p.GetLabelRoles(id) 375 376 if err != nil { 377 writeGeneralSQLError(w, method, err) 378 return 379 } 380 381 // Store all previous roles as map for easy querying 382 previousRoleUsers := make(map[string]bool) 383 384 for _, v := range previousRoles { 385 previousRoleUsers[v.UserID] = true 386 } 387 388 // Who is sharing this folder? 389 inviter, err := p.GetUser(p.Context.UserID) 390 391 if err != nil { 392 log.IfErr(tx.Rollback()) 393 writeGeneralSQLError(w, method, err) 394 return 395 } 396 397 // Nuke all previous permissions for this folder 398 _, err = p.DeleteLabelRoles(id) 399 400 if err != nil { 401 log.IfErr(tx.Rollback()) 402 writeGeneralSQLError(w, method, err) 403 return 404 } 405 406 me := false 407 hasEveryoneRole := false 408 roleCount := 0 409 410 url := getAppURL(p.Context, fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 411 412 for _, role := range model.Roles { 413 role.OrgID = p.Context.OrgID 414 role.LabelID = id 415 416 // Ensure the folder owner always has access! 417 if role.UserID == p.Context.UserID { 418 me = true 419 role.CanView = true 420 role.CanEdit = true 421 } 422 423 if len(role.UserID) == 0 && (role.CanView || role.CanEdit) { 424 hasEveryoneRole = true 425 } 426 427 // Only persist if there is a role! 428 if role.CanView || role.CanEdit { 429 roleID := util.UniqueID() 430 role.RefID = roleID 431 err = p.AddLabelRole(role) 432 roleCount++ 433 log.IfErr(err) 434 435 // We send out folder invitation emails to those users 436 // that have *just* been given permissions. 437 if _, isExisting := previousRoleUsers[role.UserID]; !isExisting { 438 439 // we skip 'everyone' (user id != empty string) 440 if len(role.UserID) > 0 { 441 var existingUser entity.User 442 existingUser, err = p.GetUser(role.UserID) 443 444 if err == nil { 445 go mail.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, label.Name, model.Message) 446 log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, existingUser.Email)) 447 } else { 448 writeServerError(w, method, err) 449 } 450 } 451 } 452 } 453 } 454 455 // Do we need to ensure permissions for folder owner when shared? 456 if !me { 457 role := entity.LabelRole{} 458 role.LabelID = id 459 role.OrgID = p.Context.OrgID 460 role.UserID = p.Context.UserID 461 role.CanEdit = true 462 role.CanView = true 463 roleID := util.UniqueID() 464 role.RefID = roleID 465 err = p.AddLabelRole(role) 466 log.IfErr(err) 467 } 468 469 // Mark up folder type as either public, private or restricted access. 470 if hasEveryoneRole { 471 label.Type = entity.FolderTypePublic 472 } else { 473 if roleCount > 1 { 474 label.Type = entity.FolderTypeRestricted 475 } else { 476 label.Type = entity.FolderTypePrivate 477 } 478 } 479 480 log.Error("p.UpdateLabel()", p.UpdateLabel(label)) 481 482 log.Error("tx.Commit()", tx.Commit()) 483 484 writeSuccessEmptyJSON(w) 485 } 486 487 // GetFolderPermissions returns user permissions for the requested folder. 488 func GetFolderPermissions(w http.ResponseWriter, r *http.Request) { 489 method := "GetFolderPermissions" 490 p := request.GetPersister(r) 491 492 params := mux.Vars(r) 493 folderID := params["folderID"] 494 495 if len(folderID) == 0 { 496 writeMissingDataError(w, method, "folderID") 497 return 498 } 499 500 roles, err := p.GetLabelRoles(folderID) 501 502 if err != nil && err != sql.ErrNoRows { 503 writeGeneralSQLError(w, method, err) 504 return 505 } 506 507 if len(roles) == 0 { 508 roles = []entity.LabelRole{} 509 } 510 511 json, err := json.Marshal(roles) 512 513 if err != nil { 514 writeJSONMarshalError(w, method, "folder-permissions", err) 515 return 516 } 517 518 writeSuccessBytes(w, json) 519 } 520 521 // AcceptSharedFolder records the fact that a user has completed folder onboard process. 522 func AcceptSharedFolder(w http.ResponseWriter, r *http.Request) { 523 method := "AcceptSharedFolder" 524 p := request.GetPersister(r) 525 526 params := mux.Vars(r) 527 folderID := params["folderID"] 528 529 if len(folderID) == 0 { 530 writeMissingDataError(w, method, "folderID") 531 return 532 } 533 534 org, err := p.GetOrganizationByDomain(p.Context.Subdomain) 535 536 if err != nil { 537 writeGeneralSQLError(w, method, err) 538 return 539 } 540 541 p.Context.OrgID = org.RefID 542 543 defer utility.Close(r.Body) 544 body, err := ioutil.ReadAll(r.Body) 545 546 if err != nil { 547 writePayloadError(w, method, err) 548 return 549 } 550 551 var model = models.AcceptSharedFolderModel{} 552 err = json.Unmarshal(body, &model) 553 554 if err != nil { 555 writePayloadError(w, method, err) 556 return 557 } 558 559 if len(model.Serial) == 0 || len(model.Firstname) == 0 || len(model.Lastname) == 0 || len(model.Password) == 0 { 560 writeJSONMarshalError(w, method, "missing field data", err) 561 return 562 } 563 564 if err != nil { 565 writeGeneralSQLError(w, method, err) 566 return 567 } 568 569 user, err := p.GetUserBySerial(model.Serial) 570 571 // User has already on-boarded. 572 if err != nil && err == sql.ErrNoRows { 573 writeDuplicateError(w, method, "user") 574 return 575 } 576 577 if err != nil { 578 writeGeneralSQLError(w, method, err) 579 return 580 } 581 582 user.Firstname = model.Firstname 583 user.Lastname = model.Lastname 584 user.Initials = utility.MakeInitials(user.Firstname, user.Lastname) 585 586 tx, err := request.Db.Beginx() 587 588 if err != nil { 589 writeTransactionError(w, method, err) 590 return 591 } 592 593 p.Context.Transaction = tx 594 595 err = p.UpdateUser(user) 596 597 if err != nil { 598 log.IfErr(tx.Rollback()) 599 writeGeneralSQLError(w, method, err) 600 return 601 } 602 603 salt := util.GenerateSalt() 604 605 log.IfErr(p.UpdateUserPassword(user.RefID, salt, util.GeneratePassword(model.Password, salt))) 606 607 if err != nil { 608 log.IfErr(tx.Rollback()) 609 writeGeneralSQLError(w, method, err) 610 return 611 } 612 613 log.IfErr(tx.Commit()) 614 615 data, err := json.Marshal(user) 616 617 if err != nil { 618 writeJSONMarshalError(w, method, "user", err) 619 return 620 } 621 622 writeSuccessBytes(w, data) 623 } 624 625 // InviteToFolder sends users folder invitation emails. 626 func InviteToFolder(w http.ResponseWriter, r *http.Request) { 627 method := "InviteToFolder" 628 p := request.GetPersister(r) 629 630 params := mux.Vars(r) 631 id := params["folderID"] 632 633 if len(id) == 0 { 634 writeMissingDataError(w, method, "folderID") 635 return 636 } 637 638 label, err := p.GetLabel(id) 639 640 if err != nil { 641 writeBadRequestError(w, method, "folder not found") 642 return 643 } 644 645 if label.UserID != p.Context.UserID { 646 writeForbiddenError(w) 647 return 648 } 649 650 defer utility.Close(r.Body) 651 body, err := ioutil.ReadAll(r.Body) 652 653 if err != nil { 654 writePayloadError(w, method, err) 655 return 656 } 657 658 var model = models.FolderInvitationModel{} 659 err = json.Unmarshal(body, &model) 660 661 tx, err := request.Db.Beginx() 662 663 if err != nil { 664 writeTransactionError(w, method, err) 665 return 666 } 667 668 p.Context.Transaction = tx 669 670 inviter, err := p.GetUser(p.Context.UserID) 671 672 if err != nil { 673 writeGeneralSQLError(w, method, err) 674 return 675 } 676 677 for _, email := range model.Recipients { 678 var user entity.User 679 user, err = p.GetUserByEmail(email) 680 681 if err != nil && err != sql.ErrNoRows { 682 log.IfErr(tx.Rollback()) 683 writeGeneralSQLError(w, method, err) 684 return 685 } 686 687 if len(user.RefID) > 0 { 688 689 // Ensure they have access to this organization 690 accounts, err2 := p.GetUserAccounts(user.RefID) 691 692 if err2 != nil { 693 log.IfErr(tx.Rollback()) 694 writeGeneralSQLError(w, method, err2) 695 return 696 } 697 698 // we create if they c 699 hasAccess := false 700 for _, a := range accounts { 701 if a.OrgID == p.Context.OrgID { 702 hasAccess = true 703 } 704 } 705 706 if !hasAccess { 707 var a entity.Account 708 a.UserID = user.RefID 709 a.OrgID = p.Context.OrgID 710 a.Admin = false 711 a.Editor = false 712 accountID := util.UniqueID() 713 a.RefID = accountID 714 715 err = p.AddAccount(a) 716 717 if err != nil { 718 log.IfErr(tx.Rollback()) 719 writeGeneralSQLError(w, method, err) 720 return 721 } 722 } 723 724 // Ensure they have folder roles 725 _, err = p.DeleteUserFolderRoles(label.RefID, user.RefID) 726 log.IfErr(err) 727 728 role := entity.LabelRole{} 729 role.LabelID = label.RefID 730 role.OrgID = p.Context.OrgID 731 role.UserID = user.RefID 732 role.CanEdit = false 733 role.CanView = true 734 roleID := util.UniqueID() 735 role.RefID = roleID 736 737 err = p.AddLabelRole(role) 738 739 if err != nil { 740 log.IfErr(tx.Rollback()) 741 writeGeneralSQLError(w, method, err) 742 return 743 } 744 745 url := getAppURL(p.Context, fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 746 go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, label.Name, model.Message) 747 log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, email)) 748 } else { 749 // On-board new user 750 if strings.Contains(email, "@") { 751 url := getAppURL(p.Context, fmt.Sprintf("auth/share/%s/%s", label.RefID, utility.MakeSlug(label.Name))) 752 err = inviteNewUserToSharedFolder(p, email, inviter, url, label, model.Message) 753 754 if err != nil { 755 log.IfErr(tx.Rollback()) 756 writeServerError(w, method, err) 757 return 758 } 759 760 log.Info(fmt.Sprintf("%s is sharing space %s with new user %s", inviter.Email, label.Name, email)) 761 } 762 } 763 } 764 765 // We ensure that the folder is marked as restricted as a minimum! 766 if len(model.Recipients) > 0 && label.Type == entity.FolderTypePrivate { 767 label.Type = entity.FolderTypeRestricted 768 err = p.UpdateLabel(label) 769 770 if err != nil { 771 log.IfErr(tx.Rollback()) 772 writeServerError(w, method, err) 773 return 774 } 775 } 776 777 log.IfErr(tx.Commit()) 778 779 _, err = w.Write([]byte("{}")) 780 log.IfErr(err) 781 } 782 783 // Invite new user to a folder that someone has shared with them. 784 // We create the user account with default values and then take them 785 // through a welcome process designed to capture profile data. 786 // We add them to the organization and grant them view-only folder access. 787 func inviteNewUserToSharedFolder(p request.Persister, email string, invitedBy entity.User, 788 baseURL string, label entity.Label, invitationMessage string) (err error) { 789 790 var user = entity.User{} 791 user.Email = email 792 user.Firstname = email 793 user.Lastname = "" 794 user.Salt = util.GenerateSalt() 795 requestedPassword := util.GenerateRandomPassword() 796 user.Password = util.GeneratePassword(requestedPassword, user.Salt) 797 userID := util.UniqueID() 798 user.RefID = userID 799 800 err = p.AddUser(user) 801 802 if err != nil { 803 return 804 } 805 806 // Let's give this user access to the organization 807 var a entity.Account 808 a.UserID = userID 809 a.OrgID = p.Context.OrgID 810 a.Admin = false 811 a.Editor = false 812 accountID := util.UniqueID() 813 a.RefID = accountID 814 815 err = p.AddAccount(a) 816 817 if err != nil { 818 return 819 } 820 821 role := entity.LabelRole{} 822 role.LabelID = label.RefID 823 role.OrgID = p.Context.OrgID 824 role.UserID = userID 825 role.CanEdit = false 826 role.CanView = true 827 roleID := util.UniqueID() 828 role.RefID = roleID 829 830 err = p.AddLabelRole(role) 831 832 if err != nil { 833 return 834 } 835 836 url := fmt.Sprintf("%s/%s", baseURL, user.Salt) 837 go mail.ShareFolderNewUser(user.Email, invitedBy.Fullname(), url, label.Name, invitationMessage) 838 839 return 840 }