|
2 | 2 | from flask_restful import Resource
|
3 | 3 | from sqlalchemy import exists
|
4 | 4 | from sqlalchemy.orm import joinedload
|
5 |
| -from werkzeug.exceptions import Conflict, Forbidden, NotFound |
| 5 | +from werkzeug.exceptions import Conflict, Forbidden, InternalServerError, NotFound |
6 | 6 |
|
7 | 7 | from mwdb.core.capabilities import Capabilities
|
| 8 | +from mwdb.core.config import app_config |
| 9 | +from mwdb.core.mail import MailError, send_email_notification |
8 | 10 | from mwdb.core.plugins import hooks
|
9 | 11 | from mwdb.core.rate_limit import rate_limited_resource
|
10 | 12 | from mwdb.model import Group, Member, User, db
|
@@ -583,3 +585,145 @@ def delete(self, name, login):
|
583 | 585 | )
|
584 | 586 | schema = GroupSuccessResponseSchema()
|
585 | 587 | return schema.dump({"name": name})
|
| 588 | + |
| 589 | + |
| 590 | +@rate_limited_resource |
| 591 | +class RequestGroupInviteLinkResource(Resource): |
| 592 | + @requires_authorization |
| 593 | + def post(self, name, invited_user): |
| 594 | + """ |
| 595 | + --- |
| 596 | + summary: Request invitation link |
| 597 | + description: | |
| 598 | + Creates invitation link and sends an email to the invited user. |
| 599 | +
|
| 600 | + Invitation link works only for secified group and specified user |
| 601 | +
|
| 602 | + Requires `manage_users` capability or group_admin membership. |
| 603 | + security: |
| 604 | + - bearerAuth: [] |
| 605 | + tags: |
| 606 | + - group |
| 607 | + parameters: |
| 608 | + - in: path |
| 609 | + name: name |
| 610 | + schema: |
| 611 | + type: string |
| 612 | + description: Group name |
| 613 | + - in: path |
| 614 | + name: invited_user |
| 615 | + schema: |
| 616 | + type: string |
| 617 | + description: Invited user login |
| 618 | + responses: |
| 619 | + 200: |
| 620 | + description: When link was created successfully |
| 621 | + 400: |
| 622 | + description: When request body is invalid |
| 623 | + 403: |
| 624 | + description: | |
| 625 | + When user doesn't have enough permissions, |
| 626 | + group is immutable or invited user is pending |
| 627 | + 404: |
| 628 | + description: When invited user or group doesn't exist |
| 629 | + 409: |
| 630 | + description: When user is already a member of this group |
| 631 | + 503: |
| 632 | + description: | |
| 633 | + Request canceled due to database statement timeout. |
| 634 | + """ |
| 635 | + group_obj = (db.session.query(Group).filter(Group.name == name)).first() |
| 636 | + |
| 637 | + if group_obj is None: |
| 638 | + raise NotFound("Group does not exist or you are not it's member") |
| 639 | + |
| 640 | + member_obj = ( |
| 641 | + db.session.query(Member) |
| 642 | + .filter(Member.group_id == group_obj.id) |
| 643 | + .filter(Member.user_id == g.auth_user.id) |
| 644 | + ).first() |
| 645 | + |
| 646 | + if member_obj is None: |
| 647 | + raise NotFound("Group does not exist or you are not it's member") |
| 648 | + |
| 649 | + if not member_obj.group_admin: |
| 650 | + raise Forbidden("You do not have group_admin role") |
| 651 | + |
| 652 | + if group_obj.private or group_obj.immutable: # should it be more strict? |
| 653 | + raise Forbidden("You cannot invite users to this group") |
| 654 | + |
| 655 | + invited_user_obj = ( |
| 656 | + db.session.query(User).filter(User.login == invited_user) |
| 657 | + ).first() |
| 658 | + |
| 659 | + if invited_user_obj is None: |
| 660 | + raise NotFound("Inveted user does not exist") |
| 661 | + |
| 662 | + if invited_user_obj.pending: |
| 663 | + raise Forbidden("Invited user is pending") |
| 664 | + |
| 665 | + test_obj = ( |
| 666 | + db.session.query(Member) |
| 667 | + .filter(Member.group_id == group_obj.id) |
| 668 | + .filter(Member.user_id == invited_user_obj.id) |
| 669 | + ).first() |
| 670 | + if test_obj is not None: |
| 671 | + raise Conflict("Invited user is already a member of this group") |
| 672 | + |
| 673 | + token = invited_user_obj.generate_group_invite_token( |
| 674 | + group_obj.id, g.auth_user.login |
| 675 | + ) |
| 676 | + |
| 677 | + try: |
| 678 | + send_email_notification( |
| 679 | + "invitation", |
| 680 | + "MWDB: You have been invited to a new group", |
| 681 | + invited_user_obj.email, |
| 682 | + base_url=app_config.mwdb.base_url, |
| 683 | + login=invited_user_obj.login, |
| 684 | + group_invite_token=token, |
| 685 | + ) |
| 686 | + except MailError: |
| 687 | + logger.exception("Can't send e-mail notification") |
| 688 | + raise InternalServerError( |
| 689 | + "SMTP server needed to fulfill this request is" |
| 690 | + " not configured or unavailable." |
| 691 | + ) |
| 692 | + |
| 693 | + |
| 694 | +@rate_limited_resource |
| 695 | +class JoinGroupInviteLinkResource(Resource): |
| 696 | + @requires_authorization |
| 697 | + def post(selt): |
| 698 | + """ |
| 699 | + --- |
| 700 | + summary: Join group using invitation link |
| 701 | + description: | |
| 702 | + Join group using link |
| 703 | +
|
| 704 | + security: |
| 705 | + - bearerAuth: [] |
| 706 | + tags: |
| 707 | + - group |
| 708 | + responses: |
| 709 | + 200: |
| 710 | + description: When user joined group successfully |
| 711 | + 400: |
| 712 | + description: When request body is invalid |
| 713 | + 403: |
| 714 | + description: When there was a problem with the token |
| 715 | + 409: |
| 716 | + description: When user is already a member of this group |
| 717 | + 503: |
| 718 | + description: | |
| 719 | + Request canceled due to database statement timeout. |
| 720 | + """ |
| 721 | + token = request.args.get("token") |
| 722 | + |
| 723 | + if token is None: |
| 724 | + raise Forbidden("Token not found") |
| 725 | + |
| 726 | + invite_data = User.verify_group_invite_token(token) |
| 727 | + |
| 728 | + if invite_data is None: |
| 729 | + raise Forbidden("There was a problem while decoding your token") |
0 commit comments