Example #1
0
    def post(self):
        """
        Change password for a given user
        ---
        tags:
          - User Management

        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
                - password
              properties:
                user:
                  type: string
                  description: SOCA user
                password:
                  type: string
                  description: New password to configure

        responses:
          200:
            description: Pair of username/token is valid
          203:
            description: Invalid username/token pair
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument("user", type=str, location="form")
        parser.add_argument("password", type=str, location="form")
        args = parser.parse_args()
        user = args["user"]
        password = args["password"]
        logger.info(f"Received password reset request for {user}")
        if user.lower() in password.lower():
            return errors.all_errors("DS_PASSWORD_USERNAME_IN_PW")
        ds_password_complexity = r"(?=^.{8,64}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9\s])(?=.*[a-z])|(?=.*[^A-Za-z0-9\s])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9\s]))^.*"
        if not re.search(ds_password_complexity, password):
            return errors.all_errors("DS_PASSWORD_COMPLEXITY_ERROR")
        lambda_client = boto3.client("lambda", config=config.boto_extra_config())
        if user is None or password is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) and password (str) parameters are required")

        ds_password_reset_lambda_arn = config.Config.DIRECTORY_SERVICE_RESET_LAMBDA_ARN
        if not ds_password_reset_lambda_arn:
            return errors.all_errors("MISSING_DS_RESET_LAMBDA")

        try:
            response = lambda_client.invoke(FunctionName=ds_password_reset_lambda_arn,
                Payload=json.dumps({"Username": user, "Password": password, "DirectoryServiceId": config.Config.DIRECTORY_SERVICE_ID}, indent=2).encode("utf-8"))
            logger.info(str(response['Payload'].read()))
            return {"success": True, "message": "Password updated correctly."}, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #2
0
    def get(self):
        """
        Retrieve information for a specific group
        ---
        tags:
          - Group Management
        parameters:
          - in: body
            name: body
            schema:
            required:
              - group
            properties:
              group:
                type: string
                description: user of the SOCA user

        responses:
          200:
            description: Return user information
          203:
            description: Unknown user
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='args')
        args = parser.parse_args()
        group = args["group"]
        if group is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "group (str) parameter is required")

        group_search_base = "ou=Group," + config.Config.LDAP_BASE_DN
        group_search_scope = ldap.SCOPE_SUBTREE
        group_filter = 'cn=' + group
        try:
            conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST)
            groups = conn.search_s(group_search_base, group_search_scope, group_filter, ["cn", "memberUid"])
            if groups.__len__() == 0:
                return errors.all_errors("GROUP_DO_NOT_EXIST")

            for group in groups:
                group_base = group[0]
                members = []
                if "memberUid" in group[1].keys():
                    for member in group[1]["memberUid"]:
                        user = re.match("uid=(\w+),", member.decode("utf-8"))
                        if user:
                            members.append(user.group(1))
                        else:
                            members.append(member.decode("utf-8"))
                            # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500

            return {"success": True, "message": {"group_dn": group_base, "members": members}}

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #3
0
    def delete(self):
        """
        Delete a LDAP group
        ---
        tags:
          - Group Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Deleted user
          203:
            description: Unknown user
          400:
            description: Malformed client input
                """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='form')
        args = parser.parse_args()
        group = args["group"]
        request_user = request.headers.get("X-SOCA-USER")
        if request_user is None:
            return errors.all_errors("X-SOCA-USER_MISSING")

        if request_user == group:
            return errors.all_errors("CLIENT_OWN_RESOURCE")

        if group is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "group (str) parameter is required")
        else:
            if group.endswith(config.Config.GROUP_NAME_SUFFIX):
                pass
            else:
                group = f"{group}{config.Config.GROUP_NAME_SUFFIX}"
        try:
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            group_dn = f"cn={group},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            conn.delete_s(group_dn)
            return {"success": True, "message": "Deleted Resource."}, 200
        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #4
0
    def delete(self):
        """
        Delete a job from the queue
        ---
        tags:
          - Scheduler
        parameters:
          - in: body
            name: body
            schema:
              required:
                - job_id
              properties:
                job_id:
                  type: string
                  description: ID of the job to remove
        responses:
          200:
            description: Job submitted correctly
          500:
            description: Backend error
        """
        parser = reqparse.RequestParser()
        parser.add_argument('job_id', type=str, location='args')
        args = parser.parse_args()
        job_id = args['job_id']
        if job_id is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required")

        get_job_info = get(config.Config.FLASK_ENDPOINT + "/api/scheduler/job",
                           headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                           params={"job_id": job_id},
                           verify=False)

        if get_job_info.status_code != 200:
            return {"success": False, "message": "Unable to retrieve this job. Job may have terminated"}, 500
        else:
            job_info = get_job_info.json()["message"]
            job_owner = job_info["Job_Owner"].split("@")[0]
            request_user = request.headers.get("X-SOCA-USER")
            if request_user is None:
                return errors.all_errors("X-SOCA-USER_MISSING")
            if request_user != job_owner:
                return errors.all_errors("CLIENT_NOT_OWNER")
            try:
                qdel_command = config.Config.PBS_QDEL + " " + job_id
                try:
                    delete_job = subprocess.check_output(shlex.split(qdel_command))
                    return {"success": True, "message": "Job deleted"}
                except Exception as err:
                    return {"succes": False, "message": "Unable to execute qsub command: " + str(err)}, 500

            except Exception as err:
                return {"success": False, "message": "Unknown error: " + str(err)}, 500
    def post(self):
        """
        Validate a LDAP user/password
        ---
        tags:
          - LDAP management

        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
                - password
              properties:
                user:
                  type: string
                  description: SOCA user
                token:
                  type: string
                  description: Token associated to the user

        responses:
          200:
            description: Pair of user/token is valid
          210:
            description: Invalid user/token pair
          400:
            description: Client error
          401:
            description: Un-authorized
          500:
            description: Backend error
        """
        parser = reqparse.RequestParser()
        parser.add_argument("user", type=str, location='form')
        parser.add_argument("password", type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        password = args["password"]
        if user is None or password is None:
            return errors.all_errors(
                'CLIENT_MISSING_PARAMETER',
                "user (str) and password (str) are required.")

        try:
            logger.info(f"Received authentication request for {user}")
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(f"{user}@{config.Config.DOMAIN_NAME}", password)
            logger.info(f"Auth success")
            return {'success': True, 'message': 'User is valid'}, 200
        except Exception as err:
            logger.info(f"Auth failed")
            return errors.all_errors(type(err).__name__, err)
    def post(self):
        """
        Validate a LDAP user/password
        ---
        tags:
          - LDAP management

        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
                - password
              properties:
                user:
                  type: string
                  description: SOCA user
                token:
                  type: string
                  description: Token associated to the user

        responses:
          200:
            description: Pair of user/token is valid
          210:
            description: Invalid user/token pair
          400:
            description: Client error
          401:
            description: Un-authorized
          500:
            description: Backend error
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='form')
        parser.add_argument('password', type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        password = args["password"]
        if user is None or password is None:
            return errors.all_errors(
                'CLIENT_MISSING_PARAMETER',
                "user (str) and password (str) are required.")

        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN
        user_dn = 'uid={},ou=people,{}'.format(user, base_dn)
        try:
            conn = ldap.initialize('ldap://{}'.format(ldap_host))
            conn.bind_s(user_dn, password, ldap.AUTH_SIMPLE)
            return {'success': True, 'message': 'User is valid'}, 200
        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #7
0
    def delete(self):
        """
        Delete API key(s) associated to a user
        ---
        tags:
          - User Management
        parameters:
            - in: body
              name: body
              schema:
                required:
                  - user
                properties:
                    user:
                        type: string
                        description: user of the SOCA user

        responses:
            200:
                description: Key(s) has been deleted successfully.
            203:
                description: Unable to find a token.
            400:
               description: Client error.
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "user (str) parameter is required")
        try:
            check_existing_keys = ApiKeys.query.filter_by(
                user=user, is_active=True).all()
            if check_existing_keys:
                for key in check_existing_keys:
                    key.is_active = False
                    key.deactivated_on = datetime.datetime.utcnow()
                    db.session.commit()
                return {
                    "success": True,
                    "message": "Successfully deactivated"
                }, 200
            else:
                return errors.all_errors("NO_ACTIVE_TOKEN")

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #8
0
    def delete(self):
        """
        Delete a LDAP group
        ---
        tags:
          - Group Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Deleted user
          203:
            description: Unknown user
          400:
            description: Malformed client input
                """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='form')
        args = parser.parse_args()
        group = args["group"]

        request_user = request.headers.get("X-SOCA-USER")
        if request_user is None:
            return errors.all_errors("X-SOCA-USER_MISSING")

        if request_user == group:
            return errors.all_errors("CLIENT_OWN_RESOURCE")

        if group is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "group (str) parameter is required")

        ldap_base = config.Config.LDAP_BASE_DN
        try:
            conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST)
            conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW)
            conn.delete_s("cn=" + group + ",ou=Group," + ldap_base)
            return {"success": True, "message": "Deleted user."}, 200
        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
    def get(self):
        """
        List all LDAP users
        ---
        tags:
          - User Management
        responses:
          200:
            description: Pair of username/token is valid
          203:
            description: Invalid username/token pair
          400:
            description: Malformed client input
        """
        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN
        all_ldap_users = {}
        user_search_base = "ou=People," + base_dn
        user_search_scope = ldap.SCOPE_SUBTREE
        user_filter = 'uid=*'
        try:
            con = ldap.initialize('ldap://{}'.format(ldap_host))
            users = con.search_s(user_search_base, user_search_scope,
                                 user_filter)
            for user in users:
                user_base = user[0]
                username = user[1]['uid'][0].decode('utf-8')
                all_ldap_users[username] = user_base

            return {"success": True, "message": all_ldap_users}, 200

        except Exception as err:
            return all_errors(type(err).__name__, err)
Example #10
0
    def get(self):
        """
        Return available Linux UID/GID numbers
        ---
        tags:
          - LDAP management
        responses:
          200:
            description: Return list of UID/GID
          500:
            description: Unable to contact LDAP server
          501:
           description: Unknown error (followed by trace)
        """
        uid_in_use = []
        gid_in_use = []
        UID = 5000
        GID = 5000
        MAX_IDS = 65533  # 65534 is for "nobody" and 65535 is reserved
        try:
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            user_res = conn.search_s(
                f"ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}",
                ldap.SCOPE_SUBTREE, 'objectClass=person', ["uidNumber"])
            group_res = conn.search_s(
                f"ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}",
                ldap.SCOPE_SUBTREE, 'objectClass=group', ["gidNumber"])
            for a in user_res:
                if a[1]:
                    uid_temp = int(a[1].get('uidNumber')[0])
                    uid_in_use.append(uid_temp)

            for a in group_res:
                if a[1]:
                    gid_temp = int(a[1].get('gidNumber')[0])
                    gid_in_use.append(gid_temp)

            return {
                "success": True,
                "message": {
                    "proposed_uid":
                    choice([
                        i for i in range(UID, MAX_IDS) if i not in uid_in_use
                    ]),
                    "proposed_gid":
                    choice([
                        i for i in range(GID, MAX_IDS) if i not in gid_in_use
                    ]),
                    "uid_in_use":
                    uid_in_use,
                    "gid_in_use":
                    gid_in_use
                }
            }, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #11
0
    def post(self):
        """
        Add SUDO permission for a user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user
                token:
                  type: string
                  description: token associated to the user

        responses:
          200:
            description: Pair of user/token is valid
          203:
            description: Invalid user/token pair
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return {"success": False, "message": "user can not be empty"}, 400

        # check if user exist
        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN
        try:
            conn = ldap.initialize('ldap://{}'.format(ldap_host))
            conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW)
            dn_user = "******" + user + ",ou=Sudoers," + base_dn
            attrs = [
                    ('objectClass', ['top'.encode('utf-8'),
                                     'sudoRole'.encode('utf-8')]),
                    ('sudoHost', ['ALL'.encode('utf-8')]),
                    ('sudoUser', [str(user).encode('utf-8')]),
                    ('sudoCommand', ['ALL'.encode('utf-8')])
                ]

            conn.add_s(dn_user, attrs)
            change_user_key_scope = ApiKeys.query.filter_by(user=user, is_active=True).all()
            if change_user_key_scope:
                for key in change_user_key_scope:
                    key.scope = "sudo"
                    db.session.commit()
            return {"success": True, "message": user + " now has admin permission"}, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
    def delete(self):
        """
        Delete an EC2 AMI registered as DCV image on SOCA
        ---
        tags:
          - DCV

        parameters:
          - in: body
            name: body
            schema:
              required:
                - ami_label

              properties:
                ami_label:
                  type: string
                  description: Friendly name for your image

        responses:
          200:
            description: Pair of user/token is valid
          401:
            description: Invalid user/token pair
        """
        parser = reqparse.RequestParser()
        parser.add_argument('ami_label', type=str, location='form')

        args = parser.parse_args()
        if args["ami_label"] is None:
            return errors.all_errors('CLIENT_MISSING_PARAMETER', "ami_label (str) is required.")

        check_session = AmiList.query.filter_by(ami_label=args["ami_label"], is_active=True).first()
        if check_session:
            check_session.is_active = False
            check_session.deactivated_on = datetime.datetime.utcnow()
            try:
                db.session.commit()
                logger.info(f"AMI Label {args['ami_label']} deleted from SOCA")
                return {"success": True, "message": f"{args['ami_label']} deleted from SOCA successfully"}, 200
            except exc.SQLAlchemyError as e:
                db.session.rollback()
                logger.error(f"AMI Label {args['ami_label']} delete failed {e}")
                return errors.all_errors('IMAGE_DELETE_ERROR', f"{args['ami_label']} could not have been deleted because of {e}")
        else:
            return errors.all_errors('IMAGE_DELETE_ERROR', f"{args['ami_label']} could not be found")
    def get(self):
        """
        Check SUDO permissions for a user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                   type: string
                   description: user of the SOCA user

        responses:
          200:
            description: Pair of user/token is valid
          203:
            description: Invalid user/token pair
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='args')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return {"success": False, "message": "user can not be empty"}, 400

        try:
            logger.info(f"Checking SUDO permission for {user}")
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            conn.simple_bind_s(f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW)
            user_search_base = f"CN={user},OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            sudoers_group = config.Config.SUDOERS_GROUP
            filter_criteria = f"(&(objectClass=group)(member={user_search_base}))"
            for dn, entry in conn.search_s(config.Config.LDAP_BASE, ldap.SCOPE_SUBTREE, filter_criteria, ["cn", "member"]):
                if isinstance(entry, dict):
                    logger.info(f"Checking {sudoers_group}: {dn}, {entry}")
                    if "cn" in entry.keys():
                        if entry["cn"][0].decode("utf-8") == sudoers_group:
                            logger.info("Logger SUDOERS group detected, checking members")
                            if "member" in entry.keys():
                                for users in entry["member"]:
                                    logger.info(f"Detected sudo permission for {users}")
                                    if user_search_base.lower() == users.lower().decode("utf-8"):
                                        return {'success': True, 'message': "User has SUDO permissions."}, 200
            return {'success': False, 'message': "User does not have SUDO permissions."}, 222

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #14
0
    def get(self):
        """
        Retrieve information for a specific user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Return user information
          203:
            description: Unknown user
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='args')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "user (str) parameter is required")

        try:
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            user_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            user_search_scope = ldap.SCOPE_SUBTREE
            user_filter = f"(&(objectClass=user)(sAMAccountName={user}))"
            check_user = conn.search_s(user_search_base, user_search_scope,
                                       user_filter)
            if check_user.__len__() == 0:
                return {"success": False, "message": "Unknown user"}, 203
            else:
                return {"success": True, "message": str(check_user)}, 200

        except Exception as err:
            return {
                "success": False,
                "message": "Unknown error: " + str(err)
            }, 500
Example #15
0
    def get(self):
        """
        Check SUDO permissions for a user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                   type: string
                   description: user of the SOCA user

        responses:
          200:
            description: Pair of user/token is valid
          203:
            description: Invalid user/token pair
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='args')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return {"success": False, "message": "user can not be empty"}, 400

        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN

        try:
            con = ldap.initialize('ldap://{}'.format(ldap_host))
            sudoers_search_base = "ou=Sudoers," + base_dn
            sudoers_search_scope = ldap.SCOPE_SUBTREE
            sudoers_filter = 'cn=' + user
            is_sudo = con.search_s(sudoers_search_base, sudoers_search_scope,
                                   sudoers_filter)
            if is_sudo.__len__() > 0:
                return {
                    'success': True,
                    'message': "User has SUDO permissions."
                }, 200
            else:
                return {
                    'success': False,
                    'message': "User does not have SUDO permissions."
                }, 222

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #16
0
    def get(self):
        """
        Return available Linux UID/GID numbers
        ---
        tags:
          - LDAP management
        responses:
          200:
            description: Return list of UID/GID
          500:
            description: Unable to contact LDAP server
          501:
           description: Unknown error (followed by trace)
        """
        uid_in_use = []
        gid_in_use = []
        try:
            conn = ldap.initialize("ldap://" + config.Config.LDAP_HOST)
            user_res = conn.search_s(config.Config.LDAP_BASE_DN,
                                     ldap.SCOPE_SUBTREE, 'objectClass=Group',
                                     ["uidNumber"])
            group_res = conn.search_s(config.Config.LDAP_BASE_DN,
                                      ldap.SCOPE_SUBTREE,
                                      'objectClass=posixGroup', ["gidNumber"])

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)

        UID = 5000
        GID = 5000
        MAX_IDS = 65533  # 65534 is for "nobody" and 65535 is reserved

        for uid in user_res:
            uid_temp = int(uid[1].get('uidNumber')[0])
            uid_in_use.append(uid_temp)

        for gid in group_res:
            gid_temp = int(gid[1].get('gidNumber')[0])
            gid_in_use.append(gid_temp)

        return {
            "success": True,
            "message": {
                "proposed_uid":
                choice([i for i in range(UID, MAX_IDS)
                        if i not in uid_in_use]),
                "proposed_gid":
                choice([i for i in range(GID, MAX_IDS)
                        if i not in gid_in_use]),
                "uid_in_use":
                uid_in_use,
                "gid_in_use":
                gid_in_use
            }
        }, 200
Example #17
0
    def delete(self):
        """
        Remove SUDO permission for a user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Pair of user/token is valid
          203:
            description: Invalid user/token pair
          400:
            description: Malformed client input
         """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return {"success": False, "message": "user can not be empty"}, 400

        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN

        try:
            conn = ldap.initialize('ldap://{}'.format(ldap_host))
            conn.simple_bind_s(config.Config.ROOT_DN, config.Config.ROOT_PW)
            dn_user = "******" + user + ",ou=Sudoers," + base_dn
            conn.delete_s(dn_user)
            change_user_key_scope = ApiKeys.query.filter_by(
                user=user, is_active=True).all()
            if change_user_key_scope:
                for key in change_user_key_scope:
                    key.scope = "user"
                    db.session.commit()
            return {
                "success": True,
                "message": user + " does not have admin permission anymore"
            }, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #18
0
    def get(self):
        """
        List all LDAP groups
        ---
        tags:
          - Group Management
        responses:
          200:
            description: Group info
          400:
            description: Malformed client input
          500:
            description: Backend issue
        """
        try:
            all_ldap_groups = {}
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            group_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            filter_criteria = f"(objectClass=group)"
            groups = conn.search_s(group_search_base, ldap.SCOPE_SUBTREE,
                                   filter_criteria, ["cn", "member"])
            logger.info(
                f"Checking all AD groups with search filter {filter_criteria} and base {group_search_base}"
            )
            for group in groups:
                logger.info(f"Detected {group}")
                group_base = group[0]
                group_name = group[1]['cn'][0].decode('utf-8')
                members = []
                if "member" in group[1].keys():
                    for member in group[1]["member"]:
                        user = re.match("cn=(\w+),", member.decode("utf-8"))
                        if user:
                            members.append(user.group(1))
                        else:
                            # handle case where lDAP ownership was done outside of SOCA
                            members.append(member.decode("utf-8"))
                            # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500

                all_ldap_groups[group_name] = {
                    "group_dn": group_base,
                    "members": members
                }
            logger.info(f"Groups detected {all_ldap_groups}")
            return {"success": True, "message": all_ldap_groups}, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
    def post(self):
        """
        Add SUDO permission for a user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user
                token:
                  type: string
                  description: token associated to the user

        responses:
          200:
            description: Pair of user/token is valid
          203:
            description: Invalid user/token pair
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='form')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return {"success": False, "message": "user can not be empty"}, 400

        conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
        conn.simple_bind_s(f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}", config.Config.ROOT_PW)
        sudoers_group = config.Config.SUDOERS_GROUP_DN
        dn_user = f"cn={user},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
        logger.info(f"Adding SUDO permission for {dn_user}")
        mod_attrs = [(ldap.MOD_ADD, 'member', [dn_user.encode("utf-8")])]
        try:
            conn.modify_s(sudoers_group, mod_attrs)
            change_user_key_scope = ApiKeys.query.filter_by(user=user, is_active=True).all()
            if change_user_key_scope:
                for key in change_user_key_scope:
                    key.scope = "sudo"
                    db.session.commit()
            logger.info(f"Permission granted for {user}")
            return {"success": True, "message": f"{user} now has admin permission"}, 200
        except Exception as e:
            return errors.all_errors(type(e).__name__, e)
Example #20
0
    def get(self):
        """
        Return information for a given job
        ---
        tags:
          - Scheduler
        parameters:
          - in: body
            name: body
            schema:
              optional:
                - job_id
              properties:
                job_id:
                   type: string
                   description: ID of the job
        responses:
          200:
            description: List of all jobs
          500:
            description: Backend error
        """
        parser = reqparse.RequestParser()
        parser.add_argument('job_id', type=str, location='args')
        args = parser.parse_args()
        job_id = args['job_id']
        if job_id is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required")

        try:
            qstat_command = config.Config.PBS_QSTAT + " -f " + job_id + " -Fjson"
            try:
                get_job_info = subprocess.check_output(shlex.split(qstat_command))
                try:
                    sanitize_input = get_job_info.decode("utf-8")
                    for match in re.findall('"project":(\d+),', sanitize_input, re.MULTILINE):
                        # Clear case where project starts with digits to prevent leading zero errors
                        print(f'Detected "project":{match}, > Will be replaced to prevent int leading zero error')
                        sanitize_input = sanitize_input.replace(f'"project":{match},', f'"project":"{match}",')

                    job_info = ast.literal_eval(sanitize_input)
                except Exception as err:
                    return {"success": False, "message": "Unable to retrieve this job. Job may have terminated. Error: " + str(job_info)}, 210

                job_key = list(job_info["Jobs"].keys())[0]
                return {"success": True, "message": job_info["Jobs"][job_key]}, 200

            except Exception as err:
                return {"success": False, "message": "Unable to retrieve Job ID (job may have terminated and is no longer in the queue)"}, 210

        except Exception as err:
            return {"success": False, "message": "Unknown error: " + str(err)}, 500
    def get(self):
        """
        Retrieve information for a specific user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Return user information
          203:
            description: Unknown user
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('user', type=str, location='args')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "user (str) parameter is required")

        user_filter = 'cn=' + user
        user_search_base = "ou=People," + config.Config.LDAP_BASE_DN
        user_search_scope = ldap.SCOPE_SUBTREE
        try:
            conn = ldap.initialize('ldap://' + config.Config.LDAP_HOST)
            check_user = conn.search_s(user_search_base, user_search_scope,
                                       user_filter)
            if check_user.__len__() == 0:
                return {"success": False, "message": "Unknown user"}, 203
            else:
                return {"success": True, "message": str(check_user)}, 200

        except Exception as err:
            return {
                "success": False,
                "message": "Unknown error: " + str(err)
            }, 500
Example #22
0
    def get(self):
        """
        List all LDAP groups
        ---
        tags:
          - Group Management
        responses:
          200:
            description: Group info
          400:
            description: Malformed client input
          500:
            description: Backend issue
        """
        # List all LDAP users
        ldap_host = config.Config.LDAP_HOST
        base_dn = config.Config.LDAP_BASE_DN
        all_ldap_groups = {}
        group_search_base = "ou=Group," + base_dn
        group_search_scope = ldap.SCOPE_SUBTREE
        group_filter = 'cn=*'
        try:
            con = ldap.initialize('ldap://{}'.format(ldap_host))
            groups = con.search_s(group_search_base, group_search_scope,
                                  group_filter, ["cn", "memberUid"])
            for group in groups:
                group_base = group[0]
                group_name = group[1]['cn'][0].decode('utf-8')
                members = []
                if "memberUid" in group[1].keys():
                    for member in group[1]["memberUid"]:
                        user = re.match("uid=(\w+),", member.decode("utf-8"))
                        if user:
                            members.append(user.group(1))
                        else:
                            # handle case where lDAP ownership was done outside of SOCA
                            members.append(member.decode("utf-8"))
                            # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500

                all_ldap_groups[group_name] = {
                    "group_dn": group_base,
                    "members": members
                }

            return {"success": True, "message": all_ldap_groups}, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #23
0
    def get(self):
        """
        Return information for a given job
        ---
        tags:
          - Scheduler
        parameters:
          - in: body
            name: body
            schema:
              optional:
                - job_id
              properties:
                job_id:
                   type: string
                   description: ID of the job
        responses:
          200:
            description: List of all jobs
          500:
            description: Backend error
        """
        parser = reqparse.RequestParser()
        parser.add_argument('job_id', type=str, location='args')
        args = parser.parse_args()
        job_id = args['job_id']
        if job_id is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "job_id (str) parameter is required")

        try:
            qstat_command = config.Config.PBS_QSTAT + " -f " + job_id + " -Fjson"
            try:
                get_job_info = subprocess.check_output(shlex.split(qstat_command))
                try:
                    job_info = json.loads(((get_job_info.decode('utf-8')).rstrip().lstrip()))
                except Exception as err:
                    return {"success": False, "message": "Unable to retrieve this job. Job may have terminated. Error: " + str(job_info)}, 210

                job_key = list(job_info["Jobs"].keys())[0]
                return {"success": True, "message": job_info["Jobs"][job_key]}, 200

            except Exception as err:
                return {"succes": False, "message": "Unable to retrieve Job ID (job may have terminated and is no longer in the queue)"}, 210

        except Exception as err:
            return {"success": False, "message": "Unknown error: " + str(err)}, 500
    def get(self):
        """
        List all LDAP users
        ---
        tags:
          - User Management
        responses:
          200:
            description: Pair of username/token is valid
          203:
            description: Invalid username/token pair
          400:
            description: Malformed client input
        """
        all_ldap_users = {}
        try:
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            user_search_base = f"OU=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            filter_criteria = f"(objectClass=person)"
            logger.info(
                f"Checking all AD users with search filter {filter_criteria} and base {user_search_base}"
            )
            for dn, entry in conn.search_s(user_search_base,
                                           ldap.SCOPE_SUBTREE, filter_criteria,
                                           ["cn"]):
                all_ldap_users[entry["cn"][0].decode("utf-8")] = str(dn)
            logger.info(f"all ldap users {all_ldap_users}")
            return {"success": True, "message": all_ldap_users}, 200

        except Exception as err:
            return all_errors(type(err).__name__, err)
Example #25
0
    def put(self):
        """
        Add/Remove user to/from a LDAP group
        ---
        tags:
          - Group Management

        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
                - attribute
                - value
              properties:
                group:
                  type: string
                  description: user of the SOCA user
                user:
                  type: string
                  description: Attribute to change
                action:
                  type: string
                  description: New attribute value

        responses:
          200:
            description: LDAP attribute modified successfully
          203:
            description: User already belongs to the group
          204:
            description: User does not belong to the group
          400:
            description: Malformed client input
          401:
            description: Unable to bind LDAP (invalid credentials)
          500:
            description: Backend issue (see trace)
        """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='form')
        parser.add_argument('user', type=str, location='form')
        parser.add_argument('action', type=str, location='form')
        args = parser.parse_args()
        group = args["group"]
        user = args["user"]
        action = args["action"]
        ALLOWED_ACTIONS = ["add", "remove"]
        logger.info(
            f"Received LDAP group modification: {user} will be {action} from {group}"
        )
        if user is None or group is None or action is None:
            return errors.all_errors(
                "CLIENT_MISSING_PARAMETER",
                "user (str), group (str) and action (str) parameters are required"
            )

        if group.endswith(config.Config.GROUP_NAME_SUFFIX):
            pass
        else:
            group = f"{group}{config.Config.GROUP_NAME_SUFFIX}"

        if action not in ALLOWED_ACTIONS:
            return {
                "success": False,
                "message": "This action is not supported"
            }, 400
        try:
            logger.info(f"About to {action} {user} to {group}")
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            group_dn = f"cn={group},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            if "ou=users" in user.lower():
                user_dn = user
            else:
                user_dn = f"cn={user},ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"

            get_all_users = get(
                config.Config.FLASK_ENDPOINT + "/api/ldap/users",
                headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                verify=False)  # nosec

            if get_all_users.status_code == 200:
                all_users = get_all_users.json()["message"]
                all_users = dict(
                    (k.lower(), v.lower())
                    for k, v in all_users.items())  # force lowercase
                logger.info(f"all users: {all_users}")
                if user_dn.lower() not in all_users.values():
                    return {
                        "success": False,
                        "message": "User do not exist."
                    }, 212
            else:
                return {
                    "success":
                    False,
                    "message":
                    "Unable to retrieve list of LDAP users. " +
                    str(get_all_users._content)
                }, 500

            if action == "add":
                mod_attrs = [(ldap.MOD_ADD, 'member',
                              [user_dn.encode("utf-8")])]
            else:
                mod_attrs = [(ldap.MOD_DELETE, 'member',
                              [user_dn.encode("utf-8")])]

            logger.info(
                f"About to modify LDAP group for {user_dn}. Action {action} to group {group_dn}"
            )
            conn.modify_s(group_dn, mod_attrs)
            return {
                "success": True,
                "message": "LDAP attribute has been modified correctly"
            }, 200

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
    def get(self):
        """
        Retrieve API key of the user
        ---
        tags:
          - User Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - user
              properties:
                user:
                  type: string
                  description: user of the SOCA user

        responses:
          200:
            description: Return the token associated to the user
          203:
            description: No token detected
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument("user", type=str, location='args')
        args = parser.parse_args()
        user = args["user"]
        if user is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER", "user (str) parameter is required")

        try:
            check_existing_key = ApiKeys.query.filter_by(user=user,
                                                         is_active=True).first()
            if check_existing_key:
                return {"success": True, "message": check_existing_key.token}, 200
            else:
                try:
                    # Create an API key for the user if needed
                    user_exist = get(config.Config.FLASK_ENDPOINT + "/api/ldap/user",
                                     headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                                     params={"user": user},
                                     verify=False) # nosec
                    if user_exist.status_code == 200:
                        permissions = get(config.Config.FLASK_ENDPOINT + "/api/ldap/sudo",
                                          headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                                          params={"user": user},
                                          verify=False) # nosec

                        if permissions.status_code == 200:
                            scope = "sudo"
                        else:
                            scope = "user"
                        api_token = secrets.token_hex(16)
                        new_key = ApiKeys(user=user,
                                          token=api_token,
                                          is_active=True,
                                          scope=scope,
                                          created_on=datetime.datetime.utcnow())
                        db.session.add(new_key)
                        db.session.commit()
                        return {"success": True,
                                "message": api_token}, 200
                    else:
                        return {"success": False,
                                "message": "Not authorized"}, 401

                except Exception as err:
                    return errors.all_errors(type(err).__name__, err)
        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #27
0
    def get(self):
        """
        Retrieve information for a specific group
        ---
        tags:
          - Group Management
        parameters:
          - in: body
            name: body
            schema:
            required:
              - group
            properties:
              group:
                type: string
                description: user of the SOCA user

        responses:
          200:
            description: Return user information
          203:
            description: Unknown user
          400:
            description: Malformed client input
        """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='args')
        args = parser.parse_args()
        group = args["group"]

        if group is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "group (str) parameter is required")
        else:
            if group.endswith(config.Config.GROUP_NAME_SUFFIX):
                pass
            else:
                group = f"{group}{config.Config.GROUP_NAME_SUFFIX}"

        try:
            logger.info(f"Received group request for {group}")
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            group_search_base = f"ou=Users,OU={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            group_search_scope = ldap.SCOPE_SUBTREE
            filter_criteria = f"(&(objectClass=group)(cn={group}))"
            groups = conn.search_s(group_search_base, group_search_scope,
                                   filter_criteria)
            if groups.__len__() == 0:
                logger.info(f"{group} does not exist")
                return errors.all_errors("GROUP_DO_NOT_EXIST")

            for group in groups:
                logger.info(f"Detected {group}")
                group_base = group[0]
                members = []
                if "member" in group[1].keys():
                    for member in group[1]["member"]:
                        logger.info(f"Detected group member {member}")
                        members.append(member.decode("utf-8"))
                        # return {"success": False, "message": "Unable to retrieve memberUid for this group: " + str(group_base) + "members: "+str(group[1]["memberUid"])}, 500

            return {
                "success": True,
                "message": {
                    "group_dn": group_base,
                    "members": members
                }
            }

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
Example #28
0
    def post(self):
        """
        Create a new LDAP group
        ---
        tags:
          - Group Management
        parameters:
          - in: body
            name: body
            schema:
              required:
                - group
              optional:
                - gid
                - users
              properties:
                group:
                  type: string
                  description: Name of the group
                gid:
                  type: integer
                  description: Linux GID to be associated to the group
                users:
                  type: list
                  description: List of user(s) to add to the group


        responses:
          200:
            description: Group created
          203:
            description: Group already exist
          204:
            description: User does not exist and can't be added to the group
          400:
            description: Malformed client input
          500:
            description: Backend issue
        """
        parser = reqparse.RequestParser()
        parser.add_argument('group', type=str, location='form')
        parser.add_argument('gid', type=int, location='form')
        parser.add_argument('members', type=str,
                            location='form')  # comma separated list of users

        args = parser.parse_args()
        gid = args["gid"]
        group = args["group"]

        if args["members"] is None:
            members = []
        else:
            members = args["members"].split(",")

        get_gid = get(config.Config.FLASK_ENDPOINT + '/api/ldap/ids',
                      headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                      verify=False)  # nosec

        if get_gid.status_code == 200:
            current_ldap_gids = get_gid.json()
        else:
            return errors.all_errors("UNABLE_RETRIEVE_IDS", str(get_gid.text))

        if gid is None:
            group_id = current_ldap_gids["message"]["proposed_gid"]
        else:
            if gid in current_ldap_gids["message"]["gid_in_use"]:
                return errors.all_errors("GID_ALREADY_IN_USE")
            group_id = gid

        if group is None:
            return errors.all_errors("CLIENT_MISSING_PARAMETER",
                                     "group (str) parameter is required")
        else:
            if group.endswith(config.Config.GROUP_NAME_SUFFIX):
                pass
            else:
                group = f"{group}{config.Config.GROUP_NAME_SUFFIX}"

        try:
            logger.info(
                f"About to create new group {group} with members {members}")
            conn = ldap.initialize(f"ldap://{config.Config.DOMAIN_NAME}")
            conn.simple_bind_s(
                f"{config.Config.ROOT_USER}@{config.Config.DOMAIN_NAME}",
                config.Config.ROOT_PW)
            conn.protocol_version = 3
            conn.set_option(ldap.OPT_REFERRALS, 0)
            group_members = []
            group_dn = f"cn={group},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
            if members is not None:
                if not isinstance(members, list):
                    return {
                        "success": False,
                        "message": "users must be a valid list"
                    }, 400

                if len(members) > 0:
                    get_all_users = get(
                        config.Config.FLASK_ENDPOINT + "/api/ldap/users",
                        headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                        verify=False)  # nosec

                    if get_all_users.status_code == 200:
                        all_users = get_all_users.json()["message"]
                        all_users = dict(
                            (k.lower(), v)
                            for k, v in all_users.items())  # force lowercase
                    else:
                        return {
                            "success":
                            False,
                            "message":
                            "Unable to retrieve the list of SOCA users " +
                            str(get_all_users.json())
                        }, 212

                    for member in members:
                        #if "ou=users" in member.lower():
                        #    dn_user = member
                        #else:
                        #    dn_user = f"cn={member},ou=Users,ou={config.Config.NETBIOS},{config.Config.LDAP_BASE}"
                        logger.info(
                            f"Checking if {member} exist in {all_users}")
                        if member.lower() not in all_users.keys():
                            return {
                                "success":
                                False,
                                "message":
                                "Unable to create group because user (" +
                                member + ") does not exist."
                            }, 211
                        else:
                            group_members.append(member)

            attrs = [('objectClass',
                      ['top'.encode('utf-8'), 'group'.encode('utf-8')]),
                     ('gidNumber', [str(group_id).encode('utf-8')]),
                     ('sAMAccountName', [f"{group}".encode('utf-8')])]

            conn.add_s(group_dn, attrs)

            users_not_added = []
            for member in group_members:
                add_member_to_group = put(
                    config.Config.FLASK_ENDPOINT + "/api/ldap/group",
                    headers={"X-SOCA-TOKEN": config.Config.API_ROOT_KEY},
                    data={
                        "group": group,
                        "user": member,
                        "action": "add"
                    },
                    verify=False)  # nosec
                if add_member_to_group.status_code != 200:
                    users_not_added.append(member)

            if users_not_added.__len__() == 0:
                return {
                    "success": True,
                    "message": "Group created successfully"
                }, 200
            else:
                return {
                    "success":
                    True,
                    "message":
                    "Group created successfully but unable to add some users: "
                    + str(users_not_added)
                }, 214

        except Exception as err:
            return errors.all_errors(type(err).__name__, err)
    def get(self):
        """
        List DCV desktop sessions for a given user
        ---
        tags:
          - DCV

        parameters:
          - in: body
            name: body
            schema:
              required:
                - os
                - state
              properties:
                session_number:
                  type: string
                  description: Session Number
                os:
                  type: string
                  description: DCV session type (Windows or Linux)
                state:
                  type: string
                  description: active or inactive

                run_state:
                  type: string
                  description: The state of the desktop (running, pending, stopped ..)
        responses:
          200:
            description: Pair of user/token is valid
          401:
            description: Invalid user/token pair
        """
        parser = reqparse.RequestParser()
        parser.add_argument("os", type=str, location='args')
        parser.add_argument("is_active", type=str, location='args')
        parser.add_argument("session_number", type=str, location='args')
        parser.add_argument("state", type=str, location='args')
        args = parser.parse_args()
        logger.info(f"Received parameter for listing DCV desktop: {args}")

        user = request.headers.get("X-SOCA-USER")
        if user is None:
            return errors.all_errors("X-SOCA-USER_MISSING")

        if args["os"] is None or args["is_active"] is None:
            return errors.all_errors(
                'CLIENT_MISSING_PARAMETER',
                "os (str), is_active (str)  are required.")

        if args["os"] not in ["windows", "linux"]:
            return errors.all_errors("CLIENT_INVALID_PARAMETER",
                                     "os (str) must be windows or linux")

        if args["is_active"].lower() not in ["true", "false"]:
            return errors.all_errors("CLIENT_INVALID_PARAMETER",
                                     "is_active (str) must be true, false")

        # Retrieve sessions
        is_active = True if args["is_active"].lower() == "true" else False
        if args["os"].lower() == "windows":
            all_dcv_sessions = WindowsDCVSessions.query.filter(
                WindowsDCVSessions.user == user)

            if args["state"] is not None:
                all_dcv_sessions = all_dcv_sessions.filter(
                    WindowsDCVSessions.session_state == args["state"])
            if args["session_number"] is not None:
                all_dcv_sessions = all_dcv_sessions.filter(
                    WindowsDCVSessions.session_number ==
                    args["session_number"])
            all_dcv_sessions = all_dcv_sessions.filter(
                WindowsDCVSessions.is_active == is_active)

        else:
            all_dcv_sessions = LinuxDCVSessions.query.filter(
                LinuxDCVSessions.user == user)
            if args["state"] is not None:
                all_dcv_sessions = all_dcv_sessions.filter(
                    LinuxDCVSessions.session_state == args["state"])
            if args["session_number"] is not None:
                all_dcv_sessions = all_dcv_sessions.filter(
                    LinuxDCVSessions.session_number == args["session_number"])
            all_dcv_sessions = all_dcv_sessions.filter(
                LinuxDCVSessions.is_active == is_active)

        logger.info(f"Checking {args['os']} desktops for {user}")
        user_sessions = {}
        for session_info in all_dcv_sessions.all():
            try:
                session_number = session_info.session_number
                session_state = session_info.session_state
                tag_uuid = session_info.tag_uuid
                session_name = session_info.session_name
                session_host_private_dns = session_info.session_host_private_dns
                session_token = session_info.session_token
                session_local_admin_password = session_info.session_local_admin_password if args[
                    "os"] == "windows" else None
                if args["os"].lower() != "windows":
                    session_linux_distribution = session_info.session_linux_distribution
                session_instance_type = session_info.session_instance_type
                session_instance_id = session_info.session_instance_id
                support_hibernation = session_info.support_hibernation
                dcv_authentication_token = session_info.dcv_authentication_token
                session_id = session_info.session_id

                session_schedule = {
                    "monday":
                    f"{session_info.schedule_monday_start}-{session_info.schedule_monday_stop}",
                    "tuesday":
                    f"{session_info.schedule_tuesday_start}-{session_info.schedule_tuesday_stop}",
                    "wednesday":
                    f"{session_info.schedule_wednesday_start}-{session_info.schedule_wednesday_stop}",
                    "thursday":
                    f"{session_info.schedule_thursday_start}-{session_info.schedule_thursday_stop}",
                    "friday":
                    f"{session_info.schedule_friday_start}-{session_info.schedule_friday_stop}",
                    "saturday":
                    f"{session_info.schedule_saturday_start}-{session_info.schedule_saturday_stop}",
                    "sunday":
                    f"{session_info.schedule_sunday_start}-{session_info.schedule_sunday_stop}"
                }

                stack_name = f"{read_secretmanager.get_soca_configuration()['ClusterId']}-{session_name}-{user}"
                if args["os"].lower() == "windows":
                    host_info = get_host_info(
                        tag_uuid,
                        read_secretmanager.get_soca_configuration()
                        ["ClusterId"], "windows")
                else:
                    host_info = get_host_info(
                        tag_uuid,
                        read_secretmanager.get_soca_configuration()
                        ["ClusterId"], session_linux_distribution)

                logger.info(f"Host Info {host_info}")
                if not host_info:
                    try:
                        check_stack = client_cfn.describe_stacks(
                            StackName=stack_name)
                        logger.info(f"Host Info check_stack {check_stack}")
                        if check_stack['Stacks'][0]['StackStatus'] in [
                                'CREATE_FAILED', 'ROLLBACK_COMPLETE',
                                'ROLLBACK_FAILED'
                        ]:
                            logger.info(f"Host Info DEACTIVATE")
                            # no host detected, session no longer active
                            session_info.is_active = False
                            session_info.deactivated_on = datetime.utcnow()
                            db.session.commit()
                    except Exception as err:
                        logger.error(
                            f"Error checking CFN stack {stack_name} due to {err}"
                        )
                        session_info.is_active = False
                        session_info.deactivated_on = datetime.utcnow()
                        db.session.commit()
                else:
                    # detected EC2 host for the session
                    if not dcv_authentication_token:
                        session_info.session_host_private_dns = host_info[
                            "private_dns"]
                        session_info.session_host_private_ip = host_info[
                            "private_ip"]
                        session_info.session_instance_id = host_info[
                            "instance_id"]

                        authentication_data = json.dumps({
                            "system":
                            "windows" if args["os"].lower() == "windows" else
                            session_linux_distribution,
                            "session_instance_id":
                            host_info["instance_id"],
                            "session_token":
                            session_token,
                            "session_user":
                            user
                        })
                        session_authentication_token = base64.b64encode(
                            encrypt(authentication_data)).decode("utf-8")
                        session_info.dcv_authentication_token = session_authentication_token
                        db.session.commit()

                if "status" not in host_info.keys():
                    try:
                        check_stack = client_cfn.describe_stacks(
                            StackName=stack_name)
                        logger.info(f"Host Info check_stack {check_stack}")
                        if check_stack['Stacks'][0]['StackStatus'] in [
                                'CREATE_FAILED', 'ROLLBACK_COMPLETE',
                                'ROLLBACK_FAILED'
                        ]:
                            logger.info(f"Host Info DEACTIVATE")
                            # no host detected, session no longer active
                            session_info.is_active = False
                            session_info.deactivated_on = datetime.utcnow()
                            db.session.commit()

                    except Exception as err:
                        logger.error(
                            f"Error checking CFN stack {stack_name} due to {err}"
                        )
                        session_info.is_active = False
                        session_info.deactivated_on = datetime.utcnow()
                        db.session.commit()
                else:
                    if host_info["status"] in [
                            "stopped", "stopping"
                    ] and session_state != "stopped":
                        session_state = "stopped"
                        session_info.session_state = "stopped"
                        db.session.commit()

                if session_state == "pending" and session_host_private_dns is not False:
                    check_dcv_state = get(
                        f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/",
                        allow_redirects=False,
                        verify=False)  # nosec

                    logger.info(
                        "Checking {} for {} and received status {} ".format(
                            'https://' +
                            read_secretmanager.get_soca_configuration()
                            ['LoadBalancerDNSName'] + '/' +
                            session_host_private_dns + '/', session_info,
                            check_dcv_state.status_code))

                    if check_dcv_state.status_code == 200:
                        session_info.session_state = "running"
                        db.session.commit()

                user_sessions[session_number] = {
                    "url":
                    f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/",
                    "session_local_admin_password":
                    session_local_admin_password,
                    "session_state":
                    session_state,
                    "session_authentication_token":
                    dcv_authentication_token,
                    "session_id":
                    session_id,
                    "session_name":
                    session_name,
                    "session_instance_id":
                    session_instance_id,
                    "session_instance_type":
                    session_instance_type,
                    "tag_uuid":
                    tag_uuid,
                    "support_hibernation":
                    support_hibernation,
                    "session_schedule":
                    session_schedule,
                    "connection_string":
                    f"https://{read_secretmanager.get_soca_configuration()['LoadBalancerDNSName']}/{session_host_private_dns}/?authToken={dcv_authentication_token}#{session_id}"
                }

                #logger.info(user_sessions)
            except Exception as err:
                exc_type, exc_obj, exc_tb = sys.exc_info()
                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                logger.error(exc_type, fname, exc_tb.tb_lineno)
                return errors.all_errors(type(err).__name__, err)

        return {"success": True, "message": user_sessions}, 200
    def post(self):
        """
        Register a new EC2 AMI as DCV image on SOCA
        ---
        tags:
          - DCV

        parameters:
          - in: body
            name: body
            schema:
              required:
                - os
                - ami_id
                - ami_label
                - root_size
              properties:
                ami_id:
                  type: string
                  description: EC2 ID of the AMI
                os:
                  type: string
                  description: Windows or Linux
                ami_label:
                  type: string
                  description: Friendly name for your image
                root_size:
                  type: string
                  description: Minimum size of your EC2 AMI


        responses:
          200:
            description: Pair of user/token is valid
          401:
            description: Invalid user/token pair
        """
        parser = reqparse.RequestParser()
        parser.add_argument('ami_id', type=str, location='form')
        parser.add_argument('os', type=str, location='form')
        parser.add_argument('ami_label', type=str, location='form')
        parser.add_argument('root_size', type=str, location='form')

        args = parser.parse_args()
        ami_id = args["ami_id"]
        ami_label = str(args["ami_label"])
        os = args["os"]

        if args["os"] is None or args["ami_label"] is None or args["ami_id"] is None or args["root_size"] is None:
            return errors.all_errors('CLIENT_MISSING_PARAMETER', "os (str), ami_id (str), ami_label (str) and root_size (str)  are required.")

        if args["os"].lower() not in ["centos7", "rhel7", "amazonlinux2", "windows"]:
            return errors.all_errors('CLIENT_MISSING_PARAMETER', "os must be centos7, rhel7, amazonlinux2, or windows")

        try:
            root_size = int(args["root_size"])
        except ValueError:
            return errors.all_errors('IMAGE_REGISTER_ERROR', f"{root_size} must be a valid integer")
        soca_labels = get_ami_info()

        # Register AMI to SOCA
        if ami_label not in soca_labels.keys():
            try:
                ec2_response = ec2_client.describe_images(ImageIds=[ami_id],
                                                          Filters=[{'Name': 'state', 'Values': ['available']}])
                if (len(ec2_response["Images"]) != 0):
                    new_ami = AmiList(ami_id=ami_id,
                                      ami_type=os.lower(),
                                      ami_label=ami_label,
                                      is_active=True,
                                      ami_root_disk_size=root_size,
                                      created_on=datetime.datetime.utcnow())
                    try:
                        db.session.add(new_ami)
                        db.session.commit()
                        return {"success": True, "message": f"{ami_id} registered successfully in SOCA as {ami_label}"}, 200
                    except SQLAlchemyError as e:
                        db.session.rollback()
                        logger.error(f"Failed Creating AMI {ami_label} {ami_id} {e}")
                        return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} registration not successful")
                else:
                    logger.error(f"{ami_id} is not available in AWS account")
                    return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} is not available in AWS account. If you just created it, make sure the state of the image is 'available' on the AWS console")
            except botocore.exceptions.ClientError as error:
                logger.error(f"Failed Creating AMI {ami_label} {ami_id} {error}")
                return errors.all_errors('IMAGE_REGISTER_ERROR', f"{ami_id} Couldn't locate {ami_id} in AWS account. Make sure you do have permission to view it")
        else:
            logger.error(f"Label already in use {ami_label}")
            return errors.all_errors('IMAGE_REGISTER_ERROR', f"Label {ami_label} already in use. Please enter a unique label")