Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
TT-98 feat: add feature flags
  • Loading branch information
PaulRC-ioet committed Jan 7, 2021
commit 89f6c51f9c9c420c94b16ee1a09269e0524888c5
Empty file.
45 changes: 45 additions & 0 deletions commons/feature_flags/features_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from azure.appconfiguration import AzureAppConfigurationClient
from time_tracker_api.security import current_user_email
import json
import os


class FeatureFlags:
AZURE_CONNECTION_STRING = os.environ.get(
'AZURE_APP_CONFIGURATION_CONNECTION_STRING'
)

def __init__(self, key: str, label: str = None):
self.key = key
self.label = label
self.configuration = self.get_configuration(self.key, self.label)

def get_configuration(self, key: str, label: str):
connection_str = self.AZURE_CONNECTION_STRING
client = AzureAppConfigurationClient.from_connection_string(
connection_str
)
configuration = client.get_configuration_setting(
key=f".appconfig.featureflag/{self.key}", label=self.label
)

return configuration

def is_toggle_enabled(self):
self.configuration = self.get_configuration(self.key, self.label)
data = json.loads(self.configuration.value)
result = data["enabled"]

return result

def is_toggle_enabled_for_user(self):
self.configuration = self.get_configuration(self.key, self.label)
data = json.loads(self.configuration.value)
list_data = data["conditions"]["client_filters"]
data2 = list_data[0]
list_users = data2["parameters"]["Audience"]["Users"]
user = current_user_email()

return (
True if user in list_users and self.is_toggle_enabled() else False
)
5 changes: 4 additions & 1 deletion requirements/commons.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
requests==2.23.0

# To create sample content in tests and API documentation
Faker==4.0.2
Faker==4.0.2

# For feature toggles
azure-appconfiguration==1.1.1
49 changes: 40 additions & 9 deletions time_tracker_api/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,43 @@
}

# For matching UUIDs
UUID_REGEX = '[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}'
UUID_REGEX = (
'[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}'
)

iss_claim_pattern = re.compile(r"(.*).b2clogin.com/(?P<tenant_id>%s)" % UUID_REGEX)
iss_claim_pattern = re.compile(
r"(.*).b2clogin.com/(?P<tenant_id>%s)" % UUID_REGEX
)

roles = {
"admin": {"name": "time-tracker-admin"},
"client": {"name": "client-role"}
"client": {"name": "client-role"},
}


def current_user_id() -> str:
oid_claim = get_token_json().get("oid")
if oid_claim is None:
abort(message='The claim "oid" is missing in the JWT', code=HTTPStatus.UNAUTHORIZED)
abort(
message='The claim "oid" is missing in the JWT',
code=HTTPStatus.UNAUTHORIZED,
)

return oid_claim


def current_user_email() -> str:
email_list_claim = get_token_json().get("emails")
if email_list_claim is None:
abort(
message='The claim "emails" is missing in the JWT',
code=HTTPStatus.UNAUTHORIZED,
)

email_user = email_list_claim[0]
return email_user


def current_role_user() -> str:
role_user = get_token_json().get("extension_role", None)
return role_user if role_user else roles.get("client").get("name")
Expand All @@ -51,13 +70,18 @@ def current_role_user() -> str:
def current_user_tenant_id() -> str:
iss_claim = get_token_json().get("iss")
if iss_claim is None:
abort(message='The claim "iss" is missing in the JWT', code=HTTPStatus.UNAUTHORIZED)
abort(
message='The claim "iss" is missing in the JWT',
code=HTTPStatus.UNAUTHORIZED,
)

tenant_id = parse_tenant_id_from_iss_claim(iss_claim)
if tenant_id is None:
abort(message='The format of the claim "iss" cannot be understood. '
'Please contact the development team.',
code=HTTPStatus.UNAUTHORIZED)
abort(
message='The format of the claim "iss" cannot be understood. '
'Please contact the development team.',
code=HTTPStatus.UNAUTHORIZED,
)

return tenant_id

Expand All @@ -66,11 +90,18 @@ def get_or_generate_dev_secret_key():
global dev_secret_key
if dev_secret_key is None:
from time_tracker_api import flask_app as app

"""
Generates a security key for development purposes
:return: str
"""
dev_secret_key = fake.password(length=16, special_chars=True, digits=True, upper_case=True, lower_case=True)
dev_secret_key = fake.password(
length=16,
special_chars=True,
digits=True,
upper_case=True,
lower_case=True,
)
if app.config.get("FLASK_DEBUG", False): # pragma: no cover
print('*********************************************************')
print("The generated secret is \"%s\"" % dev_secret_key)
Expand Down
19 changes: 19 additions & 0 deletions time_tracker_api/time_entries/time_entries_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
remove_required_constraint,
)
from time_tracker_api.time_entries.time_entries_dao import create_dao
from commons.feature_flags.features_flags import FeatureFlags

faker = Faker()

Expand Down Expand Up @@ -267,6 +268,24 @@ def get(self):
return time_entries_dao.get_lastest_entries_by_project(conditions={})


@ns.route('/featureFlags')
class featuresFlags(Resource):
@ns.doc('flags')
@ns.marshal_list_with(time_entry)
@ns.response(HTTPStatus.NOT_FOUND, 'No time entries found')
def get(self):
"""List the latest time entries"""
featureTest = FeatureFlags("ui-list-test-users")
test = featureTest.is_toggle_enabled_for_user()
if test:
return time_entries_dao.get_lastest_entries_by_project(
conditions={}
)
else:
conditions = attributes_filter.parse_args()
return time_entries_dao.get_all(conditions=conditions)


@ns.route('/<string:id>')
@ns.response(HTTPStatus.NOT_FOUND, 'This time entry does not exist')
@ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format')
Expand Down