diff --git a/locker_server/api/v1_0/users/serializers.py b/locker_server/api/v1_0/users/serializers.py index a9da678..fc7610e 100644 --- a/locker_server/api/v1_0/users/serializers.py +++ b/locker_server/api/v1_0/users/serializers.py @@ -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, diff --git a/locker_server/api/v1_0/users/views.py b/locker_server/api/v1_0/users/views.py index cf4bb07..d9885b4 100644 --- a/locker_server/api/v1_0/users/views.py +++ b/locker_server/api/v1_0/users/views.py @@ -107,6 +107,7 @@ def get_queryset(self): "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 @@ -967,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) @@ -1000,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) diff --git a/locker_server/api_orm/repositories/user_plan_repository.py b/locker_server/api_orm/repositories/user_plan_repository.py index 3348512..a805e04 100644 --- a/locker_server/api_orm/repositories/user_plan_repository.py +++ b/locker_server/api_orm/repositories/user_plan_repository.py @@ -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 @@ -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) diff --git a/locker_server/api_orm/repositories/user_repository.py b/locker_server/api_orm/repositories/user_repository.py index fe29e96..955b18d 100644 --- a/locker_server/api_orm/repositories/user_repository.py +++ b/locker_server/api_orm/repositories/user_repository.py @@ -80,25 +80,32 @@ 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") - # TODO: Update: dont use get. use POST + 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]: @@ -114,8 +121,11 @@ def list_users_orm(cls, **filters) -> List[UserORM]: 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: @@ -152,6 +162,15 @@ def list_users_orm(cls, **filters) -> List[UserORM]: users_orm = users_orm.filter(activated=False) 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( diff --git a/locker_server/core/repositories/user_plan_repository.py b/locker_server/core/repositories/user_plan_repository.py index 029de84..edcc312 100644 --- a/locker_server/core/repositories/user_plan_repository.py +++ b/locker_server/core/repositories/user_plan_repository.py @@ -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 @@ -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 diff --git a/locker_server/core/services/user_service.py b/locker_server/core/services/user_service.py index f1bb302..0748272 100644 --- a/locker_server/core/services/user_service.py +++ b/locker_server/core/services/user_service.py @@ -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 @@ -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(