Skip to content

Commit

Permalink
Merge branch 'develop' into 'main'
Browse files Browse the repository at this point in the history
Develop

See merge request locker/api-core!414
  • Loading branch information
khaitranquang committed Mar 28, 2024
2 parents 678af6c + ac7ece6 commit dffaabb
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 57 deletions.
6 changes: 3 additions & 3 deletions locker_server/api/v1_0/users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ def to_representation(self, instance):
data = {
"id": instance.user_id,
"internal_id": instance.internal_id,
"creation_data": instance.creation_date,
"revision_data": instance.revision_date,
"creation_date": instance.creation_date,
"revision_date": instance.revision_date,
"first_login": instance.first_login,
"activated": instance.activated,
"activated_data": instance.activated_date,
"activated_date": instance.activated_date,
"account_revision_date": instance.account_revision_date,
"master_password_score": instance.master_password_score,
"timeout": instance.timeout,
Expand Down
30 changes: 23 additions & 7 deletions locker_server/api/v1_0/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ def get_queryset(self):
users = self.user_service.list_users_by_admin(**{
"register_from": self.check_int_param(self.request.query_params.get("register_from")),
"register_to": self.check_int_param(self.request.query_params.get("register_to")),
"plan": self.request.query_params.get("plan"),
# "plan": self.request.query_params.get("plan"),
"can_use_plan": self.request.query_params.get("plan") or self.request.query_params.get("can_use_plan"),
"user_ids": self.request.query_params.get("user_ids"),
"utm_source": self.request.query_params.get("utm_source"),
"q": self.request.query_params.get("q"),
"activated": self.request.query_params.get("activated"),
"status": self.request.query_params.get("status"),
"device_type": self.request.query_params.get("device_type")
})
return users
Expand Down Expand Up @@ -966,10 +968,9 @@ def retrieve(self, request, *args, **kwargs):
user_id=user.user_id
)
data["items"] = ciphers_count
current_plan = self.user_service.get_current_plan(
user=user
)
data["current_plan"] = current_plan.pm_plan.alias if current_plan else None
usable_plan_alias, db_plan_alias = self.get_usable_plan(user_id=user.user_id)
data["current_plan"] = db_plan_alias
data["usable_plan"] = usable_plan_alias

return Response(status=status.HTTP_200_OK, data=data)

Expand Down Expand Up @@ -999,9 +1000,24 @@ def list_users(self, request, *args, **kwargs):
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
data = self.normalize_users_data(users_data=serializer.data)
return self.get_paginated_response(data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
data = self.normalize_users_data(users_data=serializer.data)
return Response(data)

def get_usable_plan(self, user_id):
return self.user_service.get_usable_plan_alias(user_id=user_id)

def normalize_users_data(self, users_data):
for user_data in users_data:
user_id = user_data.get("id")
usable_plan_alias, db_plan_alias = self.get_usable_plan(user_id=user_id)
user_data.update({
"current_plan": db_plan_alias,
"usable_plan": usable_plan_alias,
})
return users_data

def get_sso_token_id(self):
decoded_token = self.auth_service.decode_token(self.request.auth.access_token, secret=settings.SECRET_KEY)
Expand Down
22 changes: 21 additions & 1 deletion locker_server/api_orm/repositories/user_plan_repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ast
import math
from typing import Dict, Optional, List
from typing import Dict, Optional, List, Tuple

from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned
Expand Down Expand Up @@ -175,6 +175,26 @@ def get_user_plan(self, user_id: int) -> Optional[PMUserPlan]:
user_plan_orm = self._get_current_plan_orm(user_id=user_id)
return ModelParser.user_plan_parser().parse_user_plan(user_plan_orm=user_plan_orm)

def get_user_usable_plan_alias(self, user_id: int) -> Tuple:
"""
Get the current plan in the database and the real plan can be used
:param user_id:
:return: (tuple) the_usable_alias, db_plan_alias
"""
user_plan_orm = self._get_current_plan_orm(user_id=user_id)
if not user_plan_orm:
return None, None
db_plan_alias = user_plan_orm.pm_plan.alias
if db_plan_alias == PLAN_TYPE_PM_FREE:
# Check user is enterprise member or family member
family_member = user_plan_orm.user.pm_plan_family.first()
if family_member:
return family_member.root_user_plan.pm_plan.alias, db_plan_alias
else:
is_enterprise_member = user_plan_orm.user.enterprise_members.exist()
return PLAN_TYPE_PM_ENTERPRISE, db_plan_alias if is_enterprise_member else db_plan_alias, db_plan_alias
return db_plan_alias, db_plan_alias

def get_mobile_user_plan(self, pm_mobile_subscription: str) -> Optional[PMUserPlan]:
try:
user_plan_orm = PMUserPlanORM.objects.get(pm_mobile_subscription=pm_mobile_subscription)
Expand Down
149 changes: 105 additions & 44 deletions locker_server/api_orm/repositories/user_repository.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
import json
from datetime import timedelta, datetime
from typing import Union, Dict, Optional, Tuple, List

import requests
import stripe
from django.conf import settings
from datetime import timedelta, datetime
from typing import Dict, Optional, Tuple, List

from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Subquery, OuterRef, Count, Case, When, IntegerField, Value, Q, Sum, CharField, F, Min
from django.db.models import Subquery, OuterRef, Count, Case, When, IntegerField, Value, Q, Sum, CharField, F
from django.db.models.expressions import RawSQL
from django.forms import FloatField

from locker_server.api_orm.model_parsers.wrapper import get_model_parser
from locker_server.api_orm.models import UserScoreORM
from locker_server.api_orm.models import UserScoreORM, PMUserPlanFamilyORM
from locker_server.api_orm.models.wrapper import get_user_model, get_enterprise_member_model, get_enterprise_model, \
get_event_model, get_cipher_model, get_device_access_token_model, get_team_model, get_team_member_model, \
get_device_model
from locker_server.api_orm.utils.revision_date import bump_account_revision_date
from locker_server.core.entities.user.user import User
from locker_server.core.repositories.user_repository import UserRepository
from locker_server.shared.caching.sync_cache import delete_sync_cache_data
from locker_server.shared.constants.account import ACCOUNT_TYPE_ENTERPRISE, ACCOUNT_TYPE_PERSONAL, \
LOGIN_METHOD_PASSWORD, LOGIN_METHOD_PASSWORDLESS
from locker_server.shared.constants.backup_credential import CREDENTIAL_TYPE_HMAC
from locker_server.shared.constants.account import *
from locker_server.shared.constants.ciphers import *
from locker_server.shared.constants.enterprise_members import E_MEMBER_STATUS_CONFIRMED, E_MEMBER_ROLE_PRIMARY_ADMIN, \
E_MEMBER_ROLE_ADMIN
from locker_server.shared.constants.device_type import *
from locker_server.shared.constants.enterprise_members import *
from locker_server.shared.constants.event import EVENT_USER_BLOCK_LOGIN
from locker_server.shared.constants.members import MEMBER_ROLE_OWNER
from locker_server.shared.constants.policy import POLICY_TYPE_PASSWORDLESS, POLICY_TYPE_2FA
from locker_server.shared.constants.transactions import PAYMENT_STATUS_PAID, PLAN_TYPE_PM_FREE
from locker_server.shared.external_services.locker_background.background_factory import BackgroundFactory
from locker_server.shared.external_services.locker_background.constants import BG_NOTIFY
from locker_server.shared.constants.transactions import *
from locker_server.shared.external_services.requester.retry_requester import requester
from locker_server.shared.log.cylog import CyLog
from locker_server.shared.utils.app import now, start_end_month_current, datetime_from_ts
from locker_server.shared.utils.app import now, start_end_month_current


DeviceORM = get_device_model()
UserORM = get_user_model()
Expand Down Expand Up @@ -84,24 +79,33 @@ def _generate_duration_init_data(start, end, duration="monthly"):
def search_from_cystack_id(cls, **filter_params):
q_param = filter_params.get("q")
utm_source_param = filter_params.get("utm_source")
is_locker_param = filter_params.get("is_locker")
status_param = filter_params.get("status")
headers = {'Authorization': settings.MICRO_SERVICE_USER_AUTH}
url = "{}/micro_services/users?".format(settings.GATEWAY_API)
if q_param:
url += "&q={}".format(q_param)
if utm_source_param:
url += "&utm_source={}".format(utm_source_param)
data_send = {}
if q_param is not None:
data_send.update({"q": q_param})
if utm_source_param is not None:
data_send.update({"utm_source": utm_source_param}),
if is_locker_param is not None:
data_send.update({"is_locker": is_locker_param})
if status_param is not None:
data_send.update({"status": status_param})
try:
res = requester(method="GET", url=url, headers=headers)
res = requester(method="POST", url=url, headers=headers, data_send=data_send)
if res.status_code == 200:
try:
return res.json()
except json.JSONDecodeError:
CyLog.error(
**{"message": f"[!] User.search_from_cystack_id JSON Decode error: {res.url} {res.text}"})
return {}
return {}
CyLog.error(**{
"message": f"[!] User.search_from_cystack_id JSON Decode error: {res.url} {res.text}"
})
return []
return []
except (requests.RequestException, requests.ConnectTimeout):
return {}
CyLog.error(**{"message": f"[!] User.search_from_cystack_id Request Connect error"})
return []

@classmethod
def list_users_orm(cls, **filters) -> List[UserORM]:
Expand All @@ -110,13 +114,18 @@ def list_users_orm(cls, **filters) -> List[UserORM]:
register_from_param = filters.get("register_from")
register_to_param = filters.get("register_to")
plan_param = filters.get("plan")
can_use_plan_param = filters.get("can_use_plan")
status_param = filters.get("status")
activated_param = filters.get("activated")
user_ids_param = filters.get("user_ids")
utm_source_param = filters.get("utm_source")
device_type_param = filters.get("device_type")

if q_param or utm_source_param:
user_ids = cls.search_from_cystack_id(**{"q": q_param, "utm_source": utm_source_param}).get("ids", [])
if q_param or utm_source_param or status_param in ["unverified", "deleted"]:
users_data = cls.search_from_cystack_id(**{
"q": q_param, "utm_source": utm_source_param, "status_param": status_param, "is_locker": True
})
user_ids = [u.get("id") for u in users_data]
users_orm = users_orm.filter(user_id__in=user_ids)

if register_from_param:
Expand All @@ -128,45 +137,97 @@ def list_users_orm(cls, **filters) -> List[UserORM]:
users_orm = users_orm.filter(Q(pm_user_plan__isnull=True) | Q(pm_user_plan__pm_plan__alias=plan_param))
else:
users_orm = users_orm.filter(pm_user_plan__pm_plan__alias=plan_param)
if activated_param:
if activated_param == "0":
if can_use_plan_param:
if plan_param == PLAN_TYPE_PM_ENTERPRISE:
enterprise_user_ids = list(
EnterpriseMemberORM.objects.filter(enterprise__locked=False).exclude(
user__isnull=True
).values_list('user_id', flat=True)
)
users_orm = users_orm.filter(user_id__in=enterprise_user_ids)
elif plan_param in LIST_FAMILY_PLAN:
family_member_user_ids = list(PMUserPlanFamilyORM.objects.filter().exclude(
user__isnull=True
).values_list('user_id', flat=True))
users_orm = users_orm.filter(
Q(pm_user_plan__pm_plan__alias=plan_param) | Q(user_id__in=family_member_user_ids)
).distinct()
elif plan_param == PLAN_TYPE_PM_FREE:
users_orm = users_orm.filter(Q(pm_user_plan__isnull=True) | Q(pm_user_plan__pm_plan__alias=plan_param))
else:
users_orm = users_orm.filter(pm_user_plan__pm_plan__alias=plan_param)

if activated_param is not None:
if activated_param == "0" or activated_param is False:
users_orm = users_orm.filter(activated=False)
elif activated_param == "1":
elif activated_param == "1" or activated_param is True:
users_orm = users_orm.filter(activated=True)

if status_param is not None:
if status_param == "created_master_pwd":
users_orm = users_orm.filter(activated=True)
elif status_param == "verified":
users_data = cls.search_from_cystack_id(**{"is_activated": True, "is_locker": True})
verified_user_ids = [u.get("id") for u in users_data]
users_orm = users_orm.filter(user_id__in=verified_user_ids).exclude(activated=False)

if device_type_param:
device_users_orm = users_orm.annotate(
web_device_count=Count(
Case(When(user_devices__client_id='web', then=1), output_field=IntegerField())
Case(When(user_devices__client_id=CLIENT_ID_WEB, then=1), output_field=IntegerField())
),
mobile_device_count=Count(
Case(When(user_devices__client_id='mobile', then=1), output_field=IntegerField())
Case(When(user_devices__client_id=CLIENT_ID_MOBILE, then=1), output_field=IntegerField())
),
ios_device_count=Count(
Case(When(user_devices__device_type=1, then=1), output_field=IntegerField())
Case(When(user_devices__device_type=DEVICE_TYPE_IOS, then=1), output_field=IntegerField())
),
android_device_count=Count(
Case(When(user_devices__device_type=0, then=1), output_field=IntegerField())
Case(When(user_devices__device_type=DEVICE_TYPE_ANDROID, then=1), output_field=IntegerField())
),
extension_device_count=Count(
Case(When(user_devices__client_id='browser', then=1), output_field=IntegerField())
Case(When(user_devices__client_id=CLIENT_ID_BROWSER, then=1), output_field=IntegerField())
),
desktop_device_count=Count(
Case(When(user_devices__client_id="desktop", then=1), output_field=IntegerField())
)
Case(When(user_devices__client_id=CLIENT_ID_DESKTOP, then=1), output_field=IntegerField())
),
desktop_windows_count=Count(
Case(When(
Q(user_devices__client_id=CLIENT_ID_DESKTOP, user_devices__device_type=DEVICE_TYPE_WINDOWS),
then=1
), output_field=IntegerField())
),
desktop_linux_count=Count(
Case(When(
Q(user_devices__client_id=CLIENT_ID_DESKTOP, user_devices__device_type=DEVICE_TYPE_LINUX),
then=1
), output_field=IntegerField())
),
desktop_mac_count=Count(
Case(When(
Q(user_devices__client_id=CLIENT_ID_DESKTOP, user_devices__device_type=DEVICE_TYPE_MAC),
then=1
), output_field=IntegerField())
),
)

if device_type_param == "mobile":
device_users_orm = device_users_orm.filter(mobile_device_count__gt=0)
if device_type_param == "android":
elif device_type_param == "android":
device_users_orm = device_users_orm.filter(android_device_count__gt=0)
if device_type_param == "ios":
elif device_type_param == "ios":
device_users_orm = device_users_orm.filter(ios_device_count__gt=0)
if device_type_param == "web":
elif device_type_param == "web":
device_users_orm = device_users_orm.filter(web_device_count__gt=0)
if device_type_param == "browser":
elif device_type_param == "browser":
device_users_orm = device_users_orm.filter(extension_device_count__gt=0)
if device_type_param == "desktop":
elif device_type_param == "desktop":
device_users_orm = device_users_orm.filter(desktop_device_count__gt=0)
elif device_type_param == "desktop_windows":
device_users_orm = device_users_orm.filter(desktop_windows_count__gt=0)
elif device_type_param == "desktop_linux":
device_users_orm = device_users_orm.filter(desktop_linux_count__gt=0)
elif device_type_param == "desktop_mac":
device_users_orm = device_users_orm.filter(desktop_mac_count__gt=0)
users_orm = users_orm.filter(user_id__in=device_users_orm.values_list('user_id', flat=True))
if user_ids_param:
users_orm = users_orm.filter(user_id__in=user_ids_param.split(","))
Expand Down
6 changes: 5 additions & 1 deletion locker_server/core/repositories/user_plan_repository.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Union, Dict, Optional, List
from typing import Union, Dict, Optional, List, Tuple
from abc import ABC, abstractmethod

from locker_server.core.entities.enterprise.enterprise import Enterprise
Expand Down Expand Up @@ -28,6 +28,10 @@ def list_expiring_enterprise_plans(self) -> List[PMUserPlan]:
def get_user_plan(self, user_id: int) -> Optional[PMUserPlan]:
pass

@abstractmethod
def get_user_usable_plan_alias(self, user_id: int) -> Tuple:
pass

@abstractmethod
def get_mobile_user_plan(self, pm_mobile_subscription: str) -> Optional[PMUserPlan]:
pass
Expand Down
5 changes: 4 additions & 1 deletion locker_server/core/services/user_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Optional, List, Dict, NoReturn, Union
from typing import Optional, List, Dict, NoReturn, Union, Tuple

import jwt
from django.conf import settings
Expand Down Expand Up @@ -88,6 +88,9 @@ def __init__(self, user_repository: UserRepository,
def get_current_plan(self, user: User) -> PMUserPlan:
return self.user_plan_repository.get_user_plan(user_id=user.user_id)

def get_usable_plan_alias(self, user_id: int) -> Tuple[str, str]:
return self.user_plan_repository.get_user_usable_plan_alias(user_id=user_id)

def update_plan(self, user_id: int, plan_type_alias: str, duration: str = DURATION_MONTHLY, scope: str = None,
**kwargs):
return self.user_plan_repository.update_plan(
Expand Down
5 changes: 5 additions & 0 deletions locker_server/shared/constants/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@

PLAN_TYPE_PM_LIFETIME = "pm_lifetime_premium"
PLAN_TYPE_PM_LIFETIME_FAMILY = "pm_lifetime_family"

LIST_FAMILY_PLAN = [PLAN_TYPE_PM_FAMILY, PLAN_TYPE_PM_LIFETIME_FAMILY]

LIST_LIFETIME_PLAN = [PLAN_TYPE_PM_LIFETIME, PLAN_TYPE_PM_LIFETIME_FAMILY]

LIST_PLAN_TYPE = [PLAN_TYPE_PM_FREE, PLAN_TYPE_PM_PREMIUM, PLAN_TYPE_PM_FAMILY, PLAN_TYPE_PM_ENTERPRISE,
PLAN_TYPE_PM_LIFETIME, PLAN_TYPE_PM_LIFETIME_FAMILY]

FAMILY_MAX_MEMBER = 6

# ------------- Banking code ----------------------------- #
Expand Down

0 comments on commit dffaabb

Please sign in to comment.