Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ export DATABASE_MASTER_KEY=<db_master_key>
# export COSMOS_DATABASE_URI=AccountEndpoint=<ACCOUNT_URI>;AccountKey=<ACCOUNT_KEY>
## Also specify the database name
export DATABASE_NAME=<db_name>

## For Azure Users interaction
export MS_AUTHORITY=
export MS_CLIENT_ID=
export MS_SCOPE=
export MS_SECRET=
export MS_ENDPOINT=
3 changes: 3 additions & 0 deletions requirements/time_tracker_api/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ flask-cors==3.0.8

#JWT
PyJWT==1.7.1

#Azure
msal==1.3.0
12 changes: 8 additions & 4 deletions time_tracker_api/time_entries/time_entries_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
from utils.extend_model import (
add_project_name_to_time_entries,
add_activity_name_to_time_entries,
)
from utils import worked_time
from utils.worked_time import str_to_datetime
from utils.extend_model import (
create_in_condition,
create_custom_query_from_str,
add_user_email_to_time_entries,
)
from utils import worked_time
from utils.worked_time import str_to_datetime

from utils.azure_users import AzureConnection
from time_tracker_api.projects.projects_model import ProjectCosmosDBModel
from time_tracker_api.projects import projects_model
from time_tracker_api.database import CRUDDao, APICosmosDBDao
Expand Down Expand Up @@ -177,6 +178,9 @@ def find_all(
activity_dao = activities_model.create_dao()
activities = activity_dao.get_all()
add_activity_name_to_time_entries(time_entries, activities)

users = AzureConnection().users()
add_user_email_to_time_entries(time_entries, users)
return time_entries

def on_create(self, new_item_data: dict, event_context: EventContext):
Expand Down
7 changes: 7 additions & 0 deletions time_tracker_api/time_entries/time_entries_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@
description='Name of the activity associated with the time-entry',
example=faker.word(['development', 'QA']),
),
'owner_email': fields.String(
required=True,
title="Owner's Email",
max_length=50,
description='Email of the user that owns the time-entry',
example=faker.email(),
),
}
time_entry_response_fields.update(common_fields)

Expand Down
79 changes: 79 additions & 0 deletions utils/azure_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import msal
import os
import requests
from typing import List


class MSConfig:
def check_variables_are_defined():
auth_variables = [
'MS_CLIENT_ID',
'MS_AUTHORITY',
'MS_SECRET',
'MS_SCOPE',
'MS_ENDPOINT',
]
for var in auth_variables:
if var not in os.environ:
raise EnvironmentError(
"{} is not defined in the environment".format(var)
)

check_variables_are_defined()
CLIENT_ID = os.environ.get('MS_CLIENT_ID')
AUTHORITY = os.environ.get('MS_AUTHORITY')
SECRET = os.environ.get('MS_SECRET')
SCOPE = os.environ.get('MS_SCOPE')
ENDPOINT = os.environ.get('MS_ENDPOINT')


class BearerAuth(requests.auth.AuthBase):
def __init__(self, access_token):
self.access_token = access_token

def __call__(self, r):
r.headers["Authorization"] = f'Bearer {self.access_token}'
return r


class AzureUser:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email


class AzureConnection:
def __init__(self, config=MSConfig):
self.client = msal.ConfidentialClientApplication(
config.CLIENT_ID,
authority=config.AUTHORITY,
client_credential=config.SECRET,
)
self.config = config
self.access_token = self.get_token()

def get_token(self):
response = self.client.acquire_token_for_client(
scopes=self.config.SCOPE
)
if "access_token" in response:
return response['access_token']
else:
error_info = f"{response['error']} {response['error_description']}"
raise ValueError(error_info)

def users(self) -> List[AzureUser]:
def to_azure_user(item) -> AzureUser:
there_is_email = len(item['otherMails']) > 0
id = item['objectId']
name = item['displayName']
email = item['otherMails'][0] if there_is_email else ''
return AzureUser(id, name, email)

endpoint = f"{self.config.ENDPOINT}/users?api-version=1.6&$select=displayName,otherMails,objectId"
response = requests.get(endpoint, auth=BearerAuth(self.access_token))

assert 200 == response.status_code
assert 'value' in response.json()
return [to_azure_user(item) for item in response.json()['value']]
7 changes: 7 additions & 0 deletions utils/extend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ def add_activity_name_to_time_entries(time_entries, activities):
setattr(time_entry, 'activity_name', activity.name)


def add_user_email_to_time_entries(time_entries, users):
for time_entry in time_entries:
for user in users:
if time_entry.owner_id == user.id:
setattr(time_entry, 'owner_email', user.email)


def create_in_condition(
data_object: list, attr_to_filter: str = "", first_attr: str = "c.id"
):
Expand Down