diff --git a/.github/workflows/deployment-branch.yaml b/.github/workflows/deployment-branch.yaml index f1f213f397..69e7907486 100644 --- a/.github/workflows/deployment-branch.yaml +++ b/.github/workflows/deployment-branch.yaml @@ -27,7 +27,6 @@ jobs: type=raw,value=${{ github.ref_name}}-${{ github.run_number }} type=raw,value=${{ github.ref_name}} - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 diff --git a/care/abdm/api/viewsets/auth.py b/care/abdm/api/viewsets/auth.py index 892b608e01..7d9305ec01 100644 --- a/care/abdm/api/viewsets/auth.py +++ b/care/abdm/api/viewsets/auth.py @@ -34,7 +34,9 @@ class OnInitView(GenericAPIView): def post(self, request, *args, **kwargs): data = request.data print("on-init", data) + AbdmGateway().confirm(data["auth"]["transactionId"], data["resp"]["requestId"]) + return Response({}, status=status.HTTP_202_ACCEPTED) @@ -72,6 +74,25 @@ def post(self, request, *args, **kwargs): return Response({}, status=status.HTTP_202_ACCEPTED) +class AuthNotifyView(GenericAPIView): + permission_classes = (AllowAny,) + authentication_classes = [ABDMAuthentication] + + def post(self, request, *args, **kwargs): + data = request.data + print("auth-notify", data) + + if data["auth"]["status"] != "GRANTED": + return + + AbdmGateway.auth_on_notify({"request_id": data["auth"]["transactionId"]}) + + # AbdmGateway().add_care_context( + # data["auth"]["accessToken"], + # data["resp"]["requestId"], + # ) + + class OnAddContextsView(GenericAPIView): permission_classes = (AllowAny,) authentication_classes = [ABDMAuthentication] @@ -236,8 +257,8 @@ def post(self, request, *args, **kwargs): # TODO: uncomment later consent_id = data["hiRequest"]["consent"]["id"] consent = json.loads(cache.get(consent_id)) if consent_id in cache else None - # if not consent or not consent["notification"]["status"] == "GRANTED": - # return Response({}, status=status.HTTP_401_UNAUTHORIZED) + if not consent or not consent["notification"]["status"] == "GRANTED": + return Response({}, status=status.HTTP_401_UNAUTHORIZED) # TODO: check if from and to are in range and consent expiry is greater than today # consent_from = datetime.fromisoformat( @@ -314,6 +335,10 @@ def post(self, request, *args, **kwargs): } ) + print("______________________________________________") + print(consent["notification"]["consentDetail"]["careContexts"][:-2:-1]) + print("______________________________________________") + AbdmGateway().data_notify( { "consent_id": data["hiRequest"]["consent"]["id"], diff --git a/care/abdm/api/viewsets/healthid.py b/care/abdm/api/viewsets/healthid.py index 4b2b6ef39b..c584a11d54 100644 --- a/care/abdm/api/viewsets/healthid.py +++ b/care/abdm/api/viewsets/healthid.py @@ -230,7 +230,7 @@ def create_health_id(self, request): { "txn_id": data["txnId"], "access_token": abha_profile["token"], - "refresh_token": None, + "refresh_token": abha_profile["refreshToken"], }, ) @@ -281,6 +281,35 @@ def search_by_health_id(self, request): response = HealthIdGateway().search_by_health_id(data) return Response(response, status=status.HTTP_200_OK) + @action(detail=False, methods=["post"]) + def get_abha_card(self, request): + data = request.data + + if ratelimit(request, "get_abha_card", [data["patient"]], increment=False): + raise CaptchaRequiredException( + detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + code=status.HTTP_429_TOO_MANY_REQUESTS, + ) + + allowed_patients = get_patient_queryset(request.user) + patient = allowed_patients.filter(external_id=data["patient"]).first() + if not patient: + raise ValidationError({"patient": "Not Found"}) + + if not patient.abha_number: + raise ValidationError({"abha": "Patient hasn't linked thier abha"}) + + if data["type"] == "png": + response = HealthIdGateway().get_abha_card_png( + {"refreshToken": patient.abha_number.refresh_token} + ) + return Response(response, status=status.HTTP_200_OK) + + response = HealthIdGateway().get_abha_card_pdf( + {"refreshToken": patient.abha_number.refresh_token} + ) + return Response(response, status=status.HTTP_200_OK) + @extend_schema( # /v1/registration/aadhaar/searchByHealthId operation_id="link_via_qr", @@ -445,11 +474,18 @@ def add_care_context(self, request, *args, **kwargs): AbdmGateway().fetch_modes( { - "healthId": consultation.patient.abha_number.abha_number, - "name": consultation.patient.abha_number.name, - "gender": consultation.patient.abha_number.gender, - "dateOfBirth": str(consultation.patient.abha_number.date_of_birth), + "healthId": consultation.patient.abha_number.health_id, + "name": request.data["name"] + if "name" in request.data + else consultation.patient.abha_number.name, + "gender": request.data["gender"] + if "gender" in request.data + else consultation.patient.abha_number.gender, + "dateOfBirth": request.data["dob"] + if "dob" in request.data + else str(consultation.patient.abha_number.date_of_birth), "consultationId": consultation_id, + # "authMode": "DIRECT", "purpose": "LINK", } ) diff --git a/care/abdm/utils/api_call.py b/care/abdm/utils/api_call.py index a84e5ff258..53ce22b426 100644 --- a/care/abdm/utils/api_call.py +++ b/care/abdm/utils/api_call.py @@ -286,6 +286,22 @@ def get_profile(self, data): response = self.api.get(path, {}, access_token) return response.json() + # /v1/account/getPngCard + def get_abha_card_png(self, data): + path = "/v1/account/getPngCard" + access_token = self.generate_access_token(data) + response = self.api.get(path, {}, access_token) + + return b64encode(response.content) + + def get_abha_card_pdf(self, data): + path = "/v1/account/getCard" + access_token = self.generate_access_token(data) + response = self.api.get(path, {}, access_token) + + return b64encode(response.content) + + # /v1/account/qrCode def get_qr_code(self, data, auth): path = "/v1/account/qrCode" @@ -391,6 +407,10 @@ def fetch_modes(self, data): """ self.temp_memory[request_id] = data + if "authMode" in data and data["authMode"] == "DIRECT": + self.init(request_id) + return + payload = { "requestId": request_id, "timestamp": str( @@ -418,6 +438,8 @@ def init(self, prev_request_id): data = self.temp_memory[prev_request_id] self.temp_memory[request_id] = data + print("auth-init", data) + payload = { "requestId": request_id, "timestamp": str( @@ -426,7 +448,7 @@ def init(self, prev_request_id): "query": { "id": data["healthId"], "purpose": data["purpose"] if "purpose" in data else "KYC_AND_LINK", - "authMode": "DEMOGRAPHICS", + "authMode": data["authMode"] if "authMode" in data else "DEMOGRAPHICS", "requester": {"type": "HIP", "id": self.hip_id}, }, } @@ -467,6 +489,24 @@ def confirm(self, transaction_id, prev_request_id): response = self.api.post(path, payload, None, additional_headers) return response + def auth_on_notify(self, data): + path = "/v0.5/links/link/on-init" + additional_headers = {"X-CM-ID": settings.X_CM_ID} + + request_id = str(uuid.uuid4()) + payload = { + "requestId": request_id, + "timestamp": str( + datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z") + ), + "acknowledgement": {"status": "OK"}, + # "error": {"code": 1000, "message": "string"}, + "resp": {"requestId": data["request_id"]}, + } + + response = self.api.post(path, payload, None, additional_headers) + return response + # TODO: make it dynamic and call it at discharge (call it from on_confirm) def add_contexts(self, data): path = "/v0.5/links/link/add-contexts" diff --git a/config/urls.py b/config/urls.py index 34cf25c36a..0b53fb37f9 100644 --- a/config/urls.py +++ b/config/urls.py @@ -10,6 +10,7 @@ ) from care.abdm.api.viewsets.auth import ( + AuthNotifyView, DiscoverView, LinkConfirmView, LinkInitView, @@ -93,6 +94,11 @@ OnConfirmView.as_view(), name="abdm_on_confirm_view", ), + path( + "v0.5/users/auth/notify", + AuthNotifyView.as_view(), + name="abdm_auth_notify_view", + ), path( "v0.5/links/link/on-add-contexts", OnAddContextsView.as_view(), diff --git a/requirements/base.txt b/requirements/base.txt index 9a154cdf9b..f78141934f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -56,3 +56,4 @@ fhir.resources==6.5.0 jwcrypto==1.5.0 # https://github.com/latchset/jwcrypto/releases pycryptodome==3.16.0 pycryptodomex==3.16.0 +pydantic==1.*