def create_project(self, data, **kwargs): """Create project row in db.""" # Lock db, get unit row and update counter unit_row = (models.Unit.query.filter_by( id=auth.current_user().unit_id).with_for_update().one_or_none()) if not unit_row: raise ddserr.AccessDeniedError( message="Error: Your user is not associated to a unit.") unit_row.counter = unit_row.counter + 1 if unit_row.counter else 1 data["public_id"] = "{}{:05d}".format(unit_row.internal_ref, unit_row.counter) # Generate bucket name data["bucket"] = self.generate_bucketname( public_id=data["public_id"], created_time=data["date_created"]) # Create project current_user = auth.current_user() new_project = models.Project( **{ **data, "unit_id": current_user.unit.id, "created_by": current_user.username }) new_project.project_statuses.append( models.ProjectStatuses(**{ "status": "In Progress", "date_created": data["date_created"], })) generate_project_key_pair(current_user, new_project) return new_project
def delete_invite(email): current_user_role = auth.current_user().role try: unanswered_invite = user_schemas.UnansweredInvite().load( {"email": email}) if unanswered_invite: if current_user_role == "Super Admin" or ( current_user_role == "Unit Admin" and unanswered_invite.role in ["Unit Admin", "Unit Personnel", "Researcher"]): db.session.delete(unanswered_invite) db.session.commit() else: raise ddserr.AccessDeniedError( message= "You do not have the correct permissions to delete this invite." ) except (sqlalchemy.exc.SQLAlchemyError, sqlalchemy.exc.OperationalError) as err: db.session.rollback() flask.current_app.logger.error( "The invite connected to the email " f"{email or '[no email provided]'} was not deleted.") raise ddserr.DatabaseError( message=str(err), alt_message=f"Failed to delete invite" + (": Database malfunction." if isinstance( err, sqlalchemy.exc.OperationalError) else "."), ) from err return email
def verify_project_access(project): """Check users access to project.""" if project not in auth.current_user().projects: raise ddserr.AccessDeniedError( message="Project access denied.", username=auth.current_user().username, project=project.public_id, ) return project
def get(self): current_user = auth.current_user() # Check that user is unit account if current_user.role not in ["Unit Admin", "Unit Personnel"]: raise ddserr.AccessDeniedError( "Access denied - only unit accounts can get invoicing information." ) # Get unit info from table (incl safespring proj name) try: unit_info = models.Unit.query.filter( models.Unit.id == sqlalchemy.func.binary( current_user.unit_id)).first() except (sqlalchemy.exc.SQLAlchemyError, sqlalchemy.exc.OperationalError) as err: flask.current_app.logger.exception(err) raise ddserr.DatabaseError( message=str(err), alt_message=f"Failed to get unit information." + (": Database malfunction." if isinstance( err, sqlalchemy.exc.OperationalError) else "."), ) from err # Total number of GB hours and cost saved in the db for the specific unit total_gbhours_db = 0.0 total_cost_db = 0.0 # Project (bucket) specific info usage = {} for p in unit_info.projects: # Define fields in usage dict usage[p.public_id] = {"gbhours": 0.0, "cost": 0.0} for f in p.files: for v in f.versions: # Calculate hours of the current file time_uploaded = v.time_uploaded time_deleted = (v.time_deleted if v.time_deleted else dds_web.utils.current_time()) file_hours = (time_deleted - time_uploaded).seconds / (60 * 60) # Calculate GBHours, if statement to avoid zerodivision exception gb_hours = ((v.size_stored / 1e9) / file_hours) if file_hours else 0.0 # Save file version gbhours to project info and increase total unit sum usage[p.public_id]["gbhours"] += gb_hours total_gbhours_db += gb_hours # Calculate approximate cost per gbhour: kr per gb per month / (days * hours) cost_gbhour = 0.09 / (30 * 24) cost = gb_hours * cost_gbhour # Save file cost to project info and increase total unit cost usage[p.public_id]["cost"] += cost total_cost_db += cost usage[p.public_id].update({ "gbhours": round(usage[p.public_id]["gbhours"], 2), "cost": round(usage[p.public_id]["cost"], 2), }) return { "total_usage": { "gbhours": round(total_gbhours_db, 2), "cost": round(total_cost_db, 2), }, "project_usage": usage, }
def post(self): # Verify that user specified json_input = flask.request.json if "email" not in json_input: raise ddserr.DDSArgumentError(message="User email missing.") try: user = user_schemas.UserSchema().load( {"email": json_input.pop("email")}) except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError( message=str(err), alt_message="Unexpected database error.") if not user: raise ddserr.NoSuchUserError() # Verify that the action is specified -- reactivate or deactivate action = json_input.get("action") if not action: raise ddserr.DDSArgumentError( message= "Please provide an action 'deactivate' or 'reactivate' for this request." ) user_email_str = user.primary_email current_user = auth.current_user() if current_user.role == "Unit Admin": # Unit Admin can only activate/deactivate Unit Admins and personnel if user.role not in ["Unit Admin", "Unit Personnel"]: raise ddserr.AccessDeniedError( message=("You can only activate/deactivate users with " "the role Unit Admin or Unit Personnel.")) if current_user.unit != user.unit: raise ddserr.AccessDeniedError(message=( "As a Unit Admin, you can only activate/deactivate other Unit Admins or " "Unit Personnel within your specific unit.")) if current_user == user: raise ddserr.AccessDeniedError( message=f"You cannot {action} your own account!") if (action == "reactivate" and user.is_active) or (action == "deactivate" and not user.is_active): raise ddserr.DDSArgumentError( message=f"User is already {action}d!") # TODO: Check if user has lost access to any projects and if so, grant access again. if action == "reactivate": user.active = True # TODO: Super admins (current_user) don't have access to projects currently, how handle this? list_of_projects = None if user.role in ["Project Owner", "Researcher"]: list_of_projects = [ x.project for x in user.project_associations ] elif user.role in ["Unit Personnel", "Unit Admin"]: list_of_projects = user.unit.projects from dds_web.api.project import ProjectAccess # Needs to be here because of circ.import ProjectAccess.give_project_access(project_list=list_of_projects, current_user=current_user, user=user) else: user.active = False try: db.session.commit() except (sqlalchemy.exc.SQLAlchemyError, sqlalchemy.exc.OperationalError) as err: db.session.rollback() raise ddserr.DatabaseError( message=str(err), alt_message=f"Unexpected database error" + (": Database malfunction." if isinstance( err, sqlalchemy.exc.OperationalError) else "."), ) from err msg = ( f"The user account {user.username} ({user_email_str}, {user.role}) " f" has been {action}d successfully been by {current_user.name} ({current_user.role})." ) flask.current_app.logger.info(msg) with structlog.threadlocal.bound_threadlocal( who={ "user": user.username, "role": user.role }, by_whom={ "user": current_user.username, "role": current_user.role }, ): action_logger.info(self.__class__) return { "message": (f"You successfully {action}d the account {user.username} " f"({user_email_str}, {user.role})!") }
def delete(self): """Request deletion of own account.""" current_user = auth.current_user() email_str = current_user.primary_email username = current_user.username proj_ids = None if current_user.role != "Super Admin": proj_ids = [proj.public_id for proj in current_user.projects] if current_user.role == "Unit Admin": num_admins = models.UnitUser.query.filter_by( unit_id=current_user.unit.id, is_admin=True).count() if num_admins <= 3: raise ddserr.AccessDeniedError(message=( f"Your unit only has {num_admins} Unit Admins. " "You cannot delete your account. " "Invite a new Unit Admin first if you wish to proceed.")) # Create URL safe token for invitation link s = itsdangerous.URLSafeTimedSerializer( flask.current_app.config["SECRET_KEY"]) token = s.dumps(email_str, salt="email-delete") # Create deletion request in database unless it already exists try: if not dds_web.utils.delrequest_exists(email_str): new_delrequest = models.DeletionRequest( **{ "requester": current_user, "email": email_str, "issued": dds_web.utils.current_time(), }) db.session.add(new_delrequest) db.session.commit() else: return { "message": ("The confirmation link has already " f"been sent to your address {email_str}!"), "status": http.HTTPStatus.OK, } except (sqlalchemy.exc.SQLAlchemyError, sqlalchemy.exc.OperationalError) as sqlerr: db.session.rollback() raise ddserr.DatabaseError( message=str(sqlerr), alt_message=f"Creation of self-deletion request failed" + (": Database malfunction." if isinstance( sqlerr, sqlalchemy.exc.OperationalError) else "."), ) from sqlerr # Create link for deletion request email link = flask.url_for("auth_blueprint.confirm_self_deletion", token=token, _external=True) subject = f"Confirm deletion of your user account {username} in the SciLifeLab Data Delivery System" msg = flask_mail.Message( subject, recipients=[email_str], ) # Need to attach the image to be able to use it msg.attach( "scilifelab_logo.png", "image/png", open( os.path.join(flask.current_app.static_folder, "img/scilifelab_logo.png"), "rb").read(), "inline", headers=[ ["Content-ID", "<Logo>"], ], ) msg.body = flask.render_template( "mail/deletion_request.txt", link=link, sender_name=current_user.name, projects=proj_ids, ) msg.html = flask.render_template( "mail/deletion_request.html", link=link, sender_name=current_user.name, projects=proj_ids, ) mail.send(msg) flask.current_app.logger.info( f"The user account {username} / {email_str} ({current_user.role}) " "has requested self-deletion.") return { "message": ("Requested account deletion initiated. An e-mail with a " f"confirmation link has been sent to your address {email_str}!"), }