Merge branch 'staging' into azure-custom-integration

This commit is contained in:
tomdds
2020-01-24 11:16:11 -05:00
committed by GitHub
192 changed files with 3127 additions and 3262 deletions

View File

@@ -159,6 +159,7 @@ def map_config(config):
"ENV": config["default"]["ENVIRONMENT"],
"BROKER_URL": config["default"]["REDIS_URI"],
"DEBUG": config["default"].getboolean("DEBUG"),
"DEBUG_MAILER": config["default"].getboolean("DEBUG_MAILER"),
"SQLALCHEMY_ECHO": config["default"].getboolean("SQLALCHEMY_ECHO"),
"PORT": int(config["default"]["PORT"]),
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
@@ -221,7 +222,7 @@ def make_config(direct_config=None):
config.read_dict({"default": direct_config})
# Assemble DATABASE_URI value
database_uri = "postgres://{}:{}@{}:{}/{}".format( # pragma: allowlist secret
database_uri = "postgresql://{}:{}@{}:{}/{}".format( # pragma: allowlist secret
config.get("default", "PGUSER"),
config.get("default", "PGPASSWORD"),
config.get("default", "PGHOST"),
@@ -289,7 +290,7 @@ def make_crl_validator(app):
def make_mailer(app):
if app.config["DEBUG"]:
if app.config["DEBUG"] or app.config["DEBUG_MAILER"]:
mailer_connection = mailer.RedisConnection(app.redis)
else:
mailer_connection = mailer.SMTPConnection(

View File

@@ -1,4 +1,13 @@
from flask import g, redirect, url_for, session, request, current_app as app
from flask import (
g,
redirect,
url_for,
session,
request,
current_app as app,
_request_ctx_stack as request_ctx_stack,
)
from werkzeug.datastructures import ImmutableTypeConversionDict
from atst.domain.users import Users
@@ -10,7 +19,6 @@ UNPROTECTED_ROUTES = [
"atst.login_redirect",
"atst.logout",
"atst.unauthorized",
"atst.helpdocs",
"static",
"atst.about",
]
@@ -57,12 +65,26 @@ def get_last_login():
return session.get("user_id") and session.get("last_login")
def _nullify_session(session):
session_key = f"{app.config.get('SESSION_KEY_PREFIX')}{session.sid}"
app.redis.delete(session_key)
request.cookies = ImmutableTypeConversionDict()
request_ctx_stack.top.session = app.session_interface.open_session(app, request)
def _current_dod_id():
return g.current_user.dod_id if session.get("user_id") else None
def logout():
if session.get("user_id"): # pragma: no branch
dod_id = g.current_user.dod_id
del session["user_id"]
del session["last_login"]
dod_id = _current_dod_id()
_nullify_session(session)
if dod_id:
app.logger.info(f"user with EDIPI {dod_id} has logged out")
else:
app.logger.info("unauthenticated user has logged out")
def _unprotected_route(request):

View File

@@ -490,6 +490,12 @@ class CloudProviderInterface:
"""
raise NotImplementedError()
def create_subscription(self, environment):
"""Returns True if a new subscription has been created or raises an
exception if an error occurs while creating a subscription.
"""
raise NotImplementedError()
class MockCloudProvider(CloudProviderInterface):
@@ -763,6 +769,11 @@ class MockCloudProvider(CloudProviderInterface):
return self._maybe(12)
def create_subscription(self, environment):
self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException)
return True
def get_calculator_url(self):
return "https://www.rackspace.com/en-us/calculator"

View File

@@ -1,14 +1,14 @@
from .forms import BaseForm, remove_empty_string
from wtforms.fields import StringField, TextAreaField, FieldList
from wtforms.validators import Required, Optional
from atst.forms.validators import ListItemRequired, ListItemsUnique
from wtforms.validators import Required, Optional, Length
from atst.forms.validators import ListItemRequired, ListItemsUnique, Name, AlphaNumeric
from atst.utils.localization import translate
class EditEnvironmentForm(BaseForm):
name = StringField(
label=translate("forms.environments.name_label"),
validators=[Required()],
validators=[Required(), Name(), Length(max=100)],
filters=[remove_empty_string],
)
@@ -16,12 +16,12 @@ class EditEnvironmentForm(BaseForm):
class NameAndDescriptionForm(BaseForm):
name = StringField(
label=translate("forms.application.name_label"),
validators=[Required()],
validators=[Required(), Name(), Length(max=100)],
filters=[remove_empty_string],
)
description = TextAreaField(
label=translate("forms.application.description_label"),
validators=[Optional()],
validators=[Optional(), Length(max=1_000)],
filters=[remove_empty_string],
)
@@ -31,6 +31,7 @@ class EnvironmentsForm(BaseForm):
StringField(
label=translate("forms.application.environment_names_label"),
filters=[remove_empty_string],
validators=[AlphaNumeric(), Length(max=100)],
),
validators=[
ListItemRequired(

View File

@@ -1,5 +1,6 @@
from flask_wtf import FlaskForm
from wtforms.fields import FormField, FieldList, HiddenField, BooleanField
from wtforms.validators import UUID
from wtforms import Form
from .member import NewForm as BaseNewMemberForm
@@ -7,11 +8,13 @@ from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.forms.fields import SelectField
from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate
from atst.forms.validators import AlphaNumeric
from wtforms.validators import Length
class EnvironmentForm(Form):
environment_id = HiddenField()
environment_name = HiddenField()
environment_id = HiddenField(validators=[UUID()])
environment_name = HiddenField(validators=[AlphaNumeric(), Length(max=100)])
role = SelectField(
environment_name,
choices=ENV_ROLES,
@@ -43,13 +46,6 @@ class PermissionsForm(FlaskForm):
"portfolios.applications.members.form.team_mgmt.description"
),
)
perms_del_env = BooleanField(
translate("portfolios.applications.members.form.del_env.label"),
default=False,
description=translate(
"portfolios.applications.members.form.del_env.description"
),
)
@property
def data(self):
@@ -63,9 +59,6 @@ class PermissionsForm(FlaskForm):
if _data["perms_team_mgmt"]:
perm_sets.append(PermissionSets.EDIT_APPLICATION_TEAM)
if _data["perms_del_env"]:
perm_sets.append(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
_data["permission_sets"] = perm_sets
return _data

View File

@@ -2,12 +2,12 @@ from flask_wtf import FlaskForm
from wtforms.validators import Required, Length
from wtforms.fields import StringField
from atst.forms.validators import IsNumber
from atst.forms.validators import Number
from atst.utils.localization import translate
class CCPOUserForm(FlaskForm):
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10, max=10), IsNumber()],
validators=[Required(), Length(min=10, max=10), Number()],
)

View File

@@ -9,22 +9,26 @@ from .forms import BaseForm
from .data import SERVICE_BRANCHES
from atst.models.user import User
from atst.utils.localization import translate
from wtforms.validators import Length
from atst.forms.validators import Number
from .validators import Name, DateRange, PhoneNumber
USER_FIELDS = {
"first_name": StringField(
translate("forms.edit_user.first_name_label"), validators=[Name()]
translate("forms.edit_user.first_name_label"),
validators=[Name(), Length(max=100)],
),
"last_name": StringField(
translate("forms.edit_user.last_name_label"), validators=[Name()]
translate("forms.edit_user.last_name_label"),
validators=[Name(), Length(max=100)],
),
"email": EmailField(translate("forms.edit_user.email_label"), validators=[Email()]),
"phone_number": TelField(
translate("forms.edit_user.phone_number_label"), validators=[PhoneNumber()]
),
"phone_ext": StringField("Extension"),
"phone_ext": StringField("Extension", validators=[Number(), Length(max=10)]),
"service_branch": SelectField(
translate("forms.edit_user.service_branch_label"), choices=SERVICE_BRANCHES
),

View File

@@ -3,16 +3,18 @@ from wtforms.fields.html5 import EmailField, TelField
from wtforms.validators import Required, Email, Length, Optional
from wtforms.fields import StringField
from atst.forms.validators import IsNumber, PhoneNumber
from atst.forms.validators import Number, PhoneNumber, Name
from atst.utils.localization import translate
class NewForm(FlaskForm):
first_name = StringField(
label=translate("forms.new_member.first_name_label"), validators=[Required()]
label=translate("forms.new_member.first_name_label"),
validators=[Required(), Name(), Length(max=100)],
)
last_name = StringField(
label=translate("forms.new_member.last_name_label"), validators=[Required()]
label=translate("forms.new_member.last_name_label"),
validators=[Required(), Name(), Length(max=100)],
)
email = EmailField(
translate("forms.new_member.email_label"), validators=[Required(), Email()]
@@ -21,8 +23,8 @@ class NewForm(FlaskForm):
translate("forms.new_member.phone_number_label"),
validators=[Optional(), PhoneNumber()],
)
phone_ext = StringField("Extension")
phone_ext = StringField("Extension", validators=[Number(), Length(max=10)])
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
validators=[Required(), Length(min=10), Number()],
)

View File

@@ -4,6 +4,7 @@ from wtforms.fields import (
TextAreaField,
)
from wtforms.validators import Length, InputRequired
from atst.forms.validators import Name
from wtforms.widgets import ListWidget, CheckboxInput
from .forms import BaseForm
@@ -20,14 +21,18 @@ class PortfolioForm(BaseForm):
min=4,
max=100,
message=translate("forms.portfolio.name.length_validation_message"),
)
),
Name(),
],
)
description = TextAreaField(translate("forms.portfolio.description.label"),)
description = TextAreaField(
translate("forms.portfolio.description.label"), validators=[Length(max=1_000)]
)
class PortfolioCreationForm(PortfolioForm):
defense_component = SelectMultipleField(
translate("forms.portfolio.defense_component.title"),
choices=SERVICE_BRANCHES,
widget=ListWidget(prefix_label=False),
option_widget=CheckboxInput(),

View File

@@ -7,9 +7,15 @@ from wtforms.fields import (
HiddenField,
)
from wtforms.fields.html5 import DateField
from wtforms.validators import Required, Length, NumberRange, ValidationError
from wtforms.validators import (
Required,
Length,
NumberRange,
ValidationError,
)
from flask_wtf import FlaskForm
from numbers import Number
import numbers
from atst.forms.validators import Number, AlphaNumeric
from .data import JEDI_CLIN_TYPES
from .fields import SelectField
@@ -17,7 +23,7 @@ from .forms import BaseForm, remove_empty_string
from atst.utils.localization import translate
from flask import current_app as app
MAX_CLIN_AMOUNT = 1000000000
MAX_CLIN_AMOUNT = 1_000_000_000
def coerce_enum(enum_inst):
@@ -29,8 +35,8 @@ def coerce_enum(enum_inst):
def validate_funding(form, field):
if (
isinstance(form.total_amount.data, Number)
and isinstance(field.data, Number)
isinstance(form.total_amount.data, numbers.Number)
and isinstance(field.data, numbers.Number)
and form.total_amount.data < field.data
):
raise ValidationError(
@@ -61,7 +67,10 @@ class CLINForm(FlaskForm):
coerce=coerce_enum,
)
number = StringField(label=translate("task_orders.form.clin_number_label"))
number = StringField(
label=translate("task_orders.form.clin_number_label"),
validators=[Number(), Length(max=4)],
)
start_date = DateField(
translate("task_orders.form.pop_start"),
description=translate("task_orders.form.pop_example"),
@@ -116,7 +125,10 @@ class AttachmentForm(BaseForm):
filename = HiddenField(
id="attachment_filename",
validators=[
Length(max=100, message=translate("forms.attachment.filename.length_error"))
Length(
max=100, message=translate("forms.attachment.filename.length_error")
),
AlphaNumeric(),
],
)
object_name = HiddenField(
@@ -124,7 +136,8 @@ class AttachmentForm(BaseForm):
validators=[
Length(
max=40, message=translate("forms.attachment.object_name.length_error")
)
),
AlphaNumeric(),
],
)
accept = ".pdf,application/pdf"
@@ -137,6 +150,7 @@ class TaskOrderForm(BaseForm):
number = StringField(
label=translate("forms.task_order.number_description"),
filters=[remove_empty_string],
validators=[Number(), Length(max=13)],
)
pdf = FormField(
AttachmentForm,

View File

@@ -2,7 +2,7 @@ from datetime import datetime
import re
from werkzeug.datastructures import FileStorage
from wtforms.validators import ValidationError
from wtforms.validators import ValidationError, Regexp
import pendulum
from atst.utils.localization import translate
@@ -31,12 +31,13 @@ def DateRange(lower_bound=None, upper_bound=None, message=None):
return _date_range
def IsNumber(message=translate("forms.validators.is_number_message")):
def Number(message=translate("forms.validators.is_number_message")):
def _is_number(form, field):
try:
int(field.data)
except (ValueError, TypeError):
raise ValidationError(message)
if field.data:
try:
int(field.data)
except (ValueError, TypeError):
raise ValidationError(message)
return _is_number
@@ -97,3 +98,7 @@ def FileLength(max_length=50000000, message=None):
field.data.seek(0)
return _file_length
def AlphaNumeric(message=translate("forms.validators.alpha_numeric_message")):
return Regexp(regex=r"^[A-Za-z0-9\-_ \.]*$", message=message)

View File

@@ -42,29 +42,11 @@ def root():
return render_template("login.html", redirect_url=redirect_url)
@bp.route("/help")
@bp.route("/help/<path:doc>")
def helpdocs(doc=None):
docs = [os.path.splitext(file)[0] for file in os.listdir("templates/help/docs")]
if doc:
return render_template("help/docs/{}.html".format(doc), docs=docs, doc=doc)
else:
return render_template("help/index.html", docs=docs, doc=doc)
@bp.route("/home")
def home():
return render_template("home.html")
@bp.route("/<path:path>")
def catch_all(path):
try:
return render_template("{}.html".format(path))
except TemplateNotFound:
raise NotFound()
def _client_s_dn():
return request.environ.get("HTTP_X_SSL_CLIENT_S_DN")

View File

@@ -26,7 +26,7 @@ def has_portfolio_applications(_user, portfolio=None, **_kwargs):
def portfolio_applications(portfolio_id):
user_env_roles = EnvironmentRoles.for_user(g.current_user.id, portfolio_id)
environment_access = {
env_role.environment_id: env_role.role for env_role in user_env_roles
env_role.environment_id: env_role.role.value for env_role in user_env_roles
}
return render_template(

View File

@@ -1,9 +1,10 @@
from flask import (
current_app as app,
g,
redirect,
render_template,
request as http_request,
url_for,
g,
)
from .blueprint import applications_bp
@@ -64,9 +65,6 @@ def filter_perm_sets_data(member):
"perms_env_mgmt": bool(
member.has_permission_set(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
),
"perms_del_env": bool(
member.has_permission_set(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
),
}
return perm_sets_data
@@ -509,11 +507,7 @@ def resend_invite(application_id, application_role_id):
token=new_invite.token,
)
flash(
"application_invite_resent",
user_name=new_invite.user_name,
application_name=app_role.application.name,
)
flash("application_invite_resent", email=new_invite.email)
else:
flash(
"application_invite_error",
@@ -529,3 +523,31 @@ def resend_invite(application_id, application_role_id):
_anchor="application-members",
)
)
@applications_bp.route(
"/environments/<environment_id>/add_subscription", methods=["POST"]
)
@user_can(Permissions.EDIT_ENVIRONMENT, message="create new environment subscription")
def create_subscription(environment_id):
environment = Environments.get(environment_id)
try:
app.csp.cloud.create_subscription(environment)
flash("environment_subscription_success", name=environment.displayname)
except GeneralCSPException:
flash("environment_subscription_failure")
return (
render_settings_page(application=environment.application, show_flash=True),
400,
)
return redirect(
url_for(
"applications.settings",
application_id=environment.application.id,
fragment="application-environments",
_anchor="application-environments",
)
)

View File

@@ -19,9 +19,6 @@ from atst.domain.exceptions import UnauthorizedError
def filter_perm_sets_data(member):
perm_sets_data = {
"perms_portfolio_mgmt": bool(
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
),
"perms_app_mgmt": bool(
member.has_permission_set(
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
@@ -33,24 +30,43 @@ def filter_perm_sets_data(member):
"perms_reporting": bool(
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_REPORTS)
),
"perms_portfolio_mgmt": bool(
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
),
}
return perm_sets_data
def filter_members_data(members_list, portfolio):
def filter_members_data(members_list):
members_data = []
for member in members_list:
members_data.append(
{
"role_id": member.id,
"user_name": member.user_name,
"permission_sets": filter_perm_sets_data(member),
"status": member.display_status,
"ppoc": PermissionSets.PORTFOLIO_POC in member.permission_sets,
# add in stuff here for forms
}
permission_sets = filter_perm_sets_data(member)
ppoc = (
PermissionSets.get(PermissionSets.PORTFOLIO_POC) in member.permission_sets
)
member_data = {
"role_id": member.id,
"user_name": member.user_name,
"permission_sets": filter_perm_sets_data(member),
"status": member.display_status,
"ppoc": ppoc,
"form": member_forms.PermissionsForm(permission_sets),
}
if not ppoc:
member_data["update_invite_form"] = (
member_forms.NewForm(user_data=member.latest_invitation)
if member.latest_invitation and member.latest_invitation.can_resend
else member_forms.NewForm()
)
member_data["invite_token"] = (
member.latest_invitation.token
if member.latest_invitation and member.latest_invitation.can_resend
else None
)
members_data.append(member_data)
return sorted(members_data, key=lambda member: member["user_name"])
@@ -75,7 +91,7 @@ def render_admin_page(portfolio, form=None):
"portfolios/admin.html",
form=form,
portfolio_form=portfolio_form,
members=filter_members_data(member_list, portfolio),
members=filter_members_data(member_list),
new_manager_form=member_forms.NewForm(),
assign_ppoc_form=assign_ppoc_form,
portfolio=portfolio,
@@ -93,26 +109,27 @@ def admin(portfolio_id):
return render_admin_page(portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>/update_ppoc", methods=["POST"])
@user_can(Permissions.EDIT_PORTFOLIO_POC, message="update portfolio ppoc")
def update_ppoc(portfolio_id):
role_id = http_request.form.get("role_id")
portfolio = Portfolios.get(g.current_user, portfolio_id)
new_ppoc_role = PortfolioRoles.get_by_id(role_id)
PortfolioRoles.make_ppoc(portfolio_role=new_ppoc_role)
flash("primary_point_of_contact_changed", ppoc_name=new_ppoc_role.full_name)
return redirect(
url_for(
"portfolios.admin",
portfolio_id=portfolio.id,
fragment="primary-point-of-contact",
_anchor="primary-point-of-contact",
)
)
# Updating PPoC is a post-MVP feature
# @portfolios_bp.route("/portfolios/<portfolio_id>/update_ppoc", methods=["POST"])
# @user_can(Permissions.EDIT_PORTFOLIO_POC, message="update portfolio ppoc")
# def update_ppoc(portfolio_id): # pragma: no cover
# role_id = http_request.form.get("role_id")
#
# portfolio = Portfolios.get(g.current_user, portfolio_id)
# new_ppoc_role = PortfolioRoles.get_by_id(role_id)
#
# PortfolioRoles.make_ppoc(portfolio_role=new_ppoc_role)
#
# flash("primary_point_of_contact_changed", ppoc_name=new_ppoc_role.full_name)
#
# return redirect(
# url_for(
# "portfolios.admin",
# portfolio_id=portfolio.id,
# fragment="primary-point-of-contact",
# _anchor="primary-point-of-contact",
# )
# )
@portfolios_bp.route("/portfolios/<portfolio_id>/edit", methods=["POST"])
@@ -166,3 +183,30 @@ def remove_member(portfolio_id, portfolio_role_id):
fragment="portfolio-members",
)
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/members/<portfolio_role_id>", methods=["POST"]
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="update portfolio members")
def update_member(portfolio_id, portfolio_role_id):
form_data = http_request.form
form = member_forms.PermissionsForm(formdata=form_data)
portfolio_role = PortfolioRoles.get_by_id(portfolio_role_id)
portfolio = Portfolios.get(user=g.current_user, portfolio_id=portfolio_id)
if form.validate() and portfolio.owner_role != portfolio_role:
PortfolioRoles.update(portfolio_role, form.data["permission_sets"])
flash("update_portfolio_member", member_name=portfolio_role.full_name)
return redirect(
url_for(
"portfolios.admin",
portfolio_id=portfolio_id,
_anchor="portfolio-members",
fragment="portfolio-members",
)
)
else:
flash("update_portfolio_member_error", member_name=portfolio_role.full_name)
return (render_admin_page(portfolio), 400)

View File

@@ -54,13 +54,22 @@ def revoke_invitation(portfolio_id, portfolio_token):
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="resend invitation")
def resend_invitation(portfolio_id, portfolio_token):
invite = PortfolioInvitations.resend(g.current_user, portfolio_token)
send_portfolio_invitation(
invitee_email=invite.email,
inviter_name=g.current_user.full_name,
token=invite.token,
)
flash("resend_portfolio_invitation", user_name=invite.user_name)
form = member_forms.NewForm(http_request.form)
if form.validate():
invite = PortfolioInvitations.resend(
g.current_user, portfolio_token, form.data["user_data"]
)
send_portfolio_invitation(
invitee_email=invite.email,
inviter_name=g.current_user.full_name,
token=invite.token,
)
flash("resend_portfolio_invitation", email=invite.email)
else:
user_name = f"{form['user_data']['first_name'].data} {form['user_data']['last_name'].data}"
flash("resend_portfolio_invitation_error", user_name=user_name)
return redirect(
url_for(
"portfolios.admin",

View File

@@ -29,7 +29,7 @@ MESSAGES = {
"category": "error",
},
"application_invite_resent": {
"title": "flash.application_invite.resent.title",
"title": None,
"message": "flash.application_invite.resent.message",
"category": "success",
},
@@ -83,6 +83,16 @@ MESSAGES = {
"message": "flash.environment.deleted.message",
"category": "success",
},
"environment_subscription_failure": {
"title": "flash.environment.subscription_failure.title",
"message": "flash.environment.subscription_failure.message",
"category": "error",
},
"environment_subscription_success": {
"title": "flash.environment.subscription_success.title",
"message": "flash.environment.subscription_success.message",
"category": "success",
},
"form_errors": {
"title": "flash.form.errors.title",
"message": "flash.form.errors.message",
@@ -90,7 +100,7 @@ MESSAGES = {
},
"insufficient_funds": {
"title": "flash.task_order.insufficient_funds.title",
"message": "",
"message": None,
"category": "warning",
},
"logged_out": {
@@ -109,8 +119,8 @@ MESSAGES = {
"category": "success",
},
"new_portfolio_member": {
"title": "flash.success",
"message": "flash.new_portfolio_member",
"title": "flash.new_portfolio_member.title",
"message": "flash.new_portfolio_member.message",
"category": "success",
},
"portfolio_member_removed": {
@@ -124,10 +134,15 @@ MESSAGES = {
"category": "success",
},
"resend_portfolio_invitation": {
"title": "flash.portfolio_invite.resent.title",
"title": None,
"message": "flash.portfolio_invite.resent.message",
"category": "success",
},
"resend_portfolio_invitation_error": {
"title": "flash.portfolio_invite.error.title",
"message": "flash.portfolio_invite.error.message",
"category": "error",
},
"revoked_portfolio_access": {
"title": "flash.portfolio_member.revoked.title",
"message": "flash.portfolio_member.revoked.message",
@@ -153,6 +168,16 @@ MESSAGES = {
"message": "flash.task_order.submitted.message",
"category": "success",
},
"update_portfolio_member": {
"title": "flash.portfolio_member.update.title",
"message": "flash.portfolio_member.update.message",
"category": "success",
},
"update_portfolio_member_error": {
"title": "flash.portfolio_member.update_error.title",
"message": "flash.portfolio_member.update_error.message",
"category": "error",
},
"updated_application_team_settings": {
"title": "flash.success",
"message": "flash.updated_application_team_settings",

View File

@@ -4,6 +4,7 @@ from atst.domain.users import Users
class SessionLimiter(object):
def __init__(self, config, session, redis):
self.limit_logins = config["LIMIT_CONCURRENT_SESSIONS"]
self.session_prefix = config.get("SESSION_KEY_PREFIX", "session:")
self.session = session
self.redis = redis
@@ -16,4 +17,4 @@ class SessionLimiter(object):
Users.update_last_session_id(user, session_id)
def _delete_session(self, session_id):
self.redis.delete("session:{}".format(session_id))
self.redis.delete(f"{self.session_prefix}{session_id}")