class Fixed(self.target_class): @extend_schema( operation_id="reset_password_confirm", request={"application/json": PasswordResetConfirmSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( DetailResponseSerializer, description="Success", examples=[ OpenApiExample( "Successful password reset", value={"detail": "Password has been reset with the new password."}, ) ], ), status.HTTP_400_BAD_REQUEST: OpenApiResponse( PasswordResetConfirmSerializer, description="Invalid input", examples=[ OpenApiExample( "Invalid uid", value={"uid": "Invalid value"}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
def id_or_type_parameter(name="id_or_type"): return OpenApiParameter( name, OpenApiTypes.STR, OpenApiParameter.PATH, required=True, description="A UUID or type string identifying the form.", examples=[ OpenApiExample( "UUID", "3fa85f64-5717-4562-b3fc-2c963f66afa6", ), OpenApiExample("Hacker Application Type", "hacker_application"), ])
class Fixed(self.target_class): @extend_schema( operation_id="register", request={"application/json": RegisterSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( response_serializer, description="successful operation", examples=[ OpenApiExample( "Successful registration", value={"detail": "Verification e-mail sent."}, status_codes=[f"{status.HTTP_200_OK}"], ) ], ), status.HTTP_400_BAD_REQUEST: OpenApiResponse( RegisterSerializer, description="Invalid input", examples=[ OpenApiExample( "Invalid email", value={"email": "Enter a valid email address."}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ), OpenApiExample( "Invalid password", value={"password2": "This password is too common."}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ), ], ), status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "Invalid auth header", description="Attempting to register with the Authorization header already set", value={"detail": "Invalid token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class ProjectRuleStatsIndexEndpoint(RuleEndpoint): private = True @extend_schema( operation_id= "Retrieve firing starts for an issue alert rule for a given time range. Results are returned in hourly buckets.", parameters=[ GLOBAL_PARAMS.ORG_SLUG, GLOBAL_PARAMS.PROJECT_SLUG, ISSUE_ALERT_PARAMS ], responses={ 200: TimeSeriesValueSerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ OpenApiExample( "Successful response", value={}, status_codes=["200"], ) ], ) def get(self, request: Request, project: Project, rule: Rule) -> Response: start, end = get_date_range_from_params(request.GET) results = fetch_rule_hourly_stats(rule, start, end) return Response( serialize(results, request.user, TimeSeriesValueSerializer()))
class ProjectRuleGroupHistoryIndexEndpoint(RuleEndpoint): @extend_schema( operation_id="Retrieve a group firing history for an issue alert", parameters=[GLOBAL_PARAMS.ORG_SLUG, GLOBAL_PARAMS.PROJECT_SLUG, ISSUE_ALERT_PARAMS], responses={ 200: RuleGroupHistorySerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ OpenApiExample( "Successful response", value={}, status_codes=["200"], ) ], ) def get(self, request: Request, project: Project, rule: Rule) -> Response: per_page = self.get_per_page(request) cursor = self.get_cursor_from_request(request) start, end = get_date_range_from_params(request.GET) results = fetch_rule_groups_paginated(rule, start, end, cursor, per_page) response = Response(serialize(results.results, request.user, RuleGroupHistorySerializer())) self.add_cursor_headers(request, response, results) return response
class Fixed(self.target_class): @extend_schema( operation_id="change_password", request={"application/json": PasswordChangeSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( DetailResponseSerializer, description="Success", examples=[ OpenApiExample( "Successful password change", value={"detail": "New password has been saved."}, status_codes=[f"{status.HTTP_200_OK}"], ) ], ), status.HTTP_400_BAD_REQUEST: OpenApiResponse( PasswordChangeSerializer, description="Invalid input", examples=[ OpenApiExample( "Password too common", value={"new_password2": "This password is too common."}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ) ], ), status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "No token", description="Invalid or missing Authorization header", value={"detail": "Authentication credentials were not provided."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class Fixed(self.target_class): @extend_schema( operation_id="login", request={"application/json": LoginSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( get_token_serializer_class(), description="Successful login", examples=[ OpenApiExample( "Success", value={"key": "491484b928d4e497ef3359a789af8ac204fc96db"}, status_codes=[f"{status.HTTP_200_OK}"], ) ], ), status.HTTP_400_BAD_REQUEST: OpenApiResponse( NonFieldErrorResponseSerializer, description="Invalid input", examples=[ OpenApiExample( "Invalid credentials", value={"non_field_errors": "Unable to log in with provided credentials"}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ) ], ), status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "Invalid auth header", description="Attempting to login with the Authorization header already set", value={"detail": "Invalid Token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class RegisterView(generics.GenericAPIView): serializer_class = RegisterSerializer @extend_schema(tags=["Register"], summary="user 등록", examples=[ OpenApiExample(request_only=True, summary="sample 1", name="sample 1", value={ "email": "*****@*****.**", "username": "******", "password": "******" }), OpenApiExample(response_only=True, summary="sample 1", name="sample 1", value={ "username": "******", "email": "*****@*****.**", }), ]) def post(self, request: Request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) user: User = serializer.save() token = RefreshToken.for_user(user).access_token current_site = get_current_site(request).domain relative_link = reverse('email-verify') abs_url = 'http://' + current_site + relative_link + "?token=" + str( token) email_body = 'Hi ' + user.username + \ ' Use the link below to verify your email \n' + abs_url data = { 'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email' } # Util.send_email(data) print("token : ", token) return Response(serializer.data, status=status.HTTP_201_CREATED)
class Fixed(self.target_class): @extend_schema( operation_id="reset_password", request={"application/json": PasswordResetSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( DetailResponseSerializer, description="Success", examples=[ OpenApiExample( "Success", value={"detail": "Password reset e-mail has been sent."}, status_codes=[f"{status.HTTP_200_OK}"], ) ], ), status.HTTP_400_BAD_REQUEST: OpenApiResponse( PasswordResetSerializer, description="Invalid input", examples=[ OpenApiExample( "User with email doesn't exist", value={"email": "The e-mail address is not assigned to any user account"}, status_codes=[f"{status.HTTP_400_BAD_REQUEST}"], ) ], ), status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "Invalid auth header", description="Attempting to reset password with the Authorization header already set", value={"detail": "Invalid token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class Fixed(self.target_class): @extend_schema( operation_id="verify_email", request={"application/json": VerifyEmailSerializer}, responses={ status.HTTP_200_OK: OpenApiResponse( DetailResponseSerializer, description="Success", examples=[OpenApiExample("Successful email verification", value={"detail": "ok"})], ), status.HTTP_404_NOT_FOUND: OpenApiResponse( DetailResponseSerializer, description="Not found", examples=[OpenApiExample("Invalid key", value={"detail": "Not found."})], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class Fixed(self.target_class): @extend_schema(operation_id="logout", **get_schema_params, tags=["auth"]) def get(self, request, *args, **kwargs): pass @extend_schema( operation_id="logout", request=None, responses={ status.HTTP_200_OK: OpenApiResponse( DetailResponseSerializer, description="Success", examples=[ OpenApiExample( "Success", value={"detail": "Successfully logged out."}, status_codes=[f"{status.HTTP_200_OK}"], ) ], ), status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "No token", value={"detail": "Invalid token header. No credentials provided."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["auth"], ) def post(self, request, *args, **kwargs): pass
class SiaeViewSet(viewsets.ReadOnlyModelViewSet): """ # Liste des SIAE La plateforme renvoie une liste de SIAE à proximité d’une ville (déterminée par son code INSEE) et dans un rayon de recherche en kilomètres autour du centre de cette ville. Les coordonnées des centre villes sont issus de [https://geo.api.gouv.fr](https://geo.api.gouv.fr/) Chaque SIAE est accompagnée d’un certain nombre de métadonnées : - SIRET - Type - Raison Sociale - Enseigne - Site web - Description de la SIAE - Blocage de toutes les candidatures OUI/NON - Adresse de la SIAE - Complément d’adresse - Code Postal - Ville - Département Chaque SIAE peut proposer 0, 1 ou plusieurs postes. Pour chaque poste renvoyé, les métadonnées fournies sont : - Appellation ROME - Date de création - Date de modification - Recrutement ouvert OUI/NON - Description du poste - Appellation modifiée """ serializer_class = SiaeSerializer filter_backends = [DjangoFilterBackend] filterset_class = SiaeOrderingFilter ordering = ["id"] # No authentication is required on this API and everybody can query anything − it’s read-only. authentication_classes = [] permission_classes = [] NOT_FOUND_RESPONSE = OpenApiExample( "Not Found", description="Not Found", value="Pas de ville avec pour code_insee 1234", response_only=True, status_codes=["404"], ) sort_description = """ Critère de tri. On peut spécifier la direction de tri : - o=critère pour l’ordre croissant - o=-critère pour l’ordre décroissant """ @extend_schema( parameters=[ OpenApiParameter(name=CODE_INSEE_PARAM_NAME, description="Filtre par code INSEE de la ville", required=True, type=str), OpenApiParameter( name=DISTANCE_FROM_CODE_INSEE_PARAM_NAME, description= f"Filtre par rayon de recherche autour de la ville, en kilomètres. Maximum {MAX_DISTANCE_RADIUS_KM} kilomètres", # noqa: E501 required=True, type=str, ), OpenApiParameter(name="format", description="Format de sortie", required=False, enum=["json", "api"]), OpenApiParameter( name="o", description=sort_description, required=False, enum=SIAE_ORDERING_FILTER_MAPPING.values(), ), ], responses={ 200: SiaeSerializer, 404: OpenApiTypes.OBJECT }, examples=[ NOT_FOUND_RESPONSE, ], ) def list(self, request): # we need this despite the default behavior because of the documentation annotations return super().list(request) def get_queryset(self): # We only get to this point if permissions are OK queryset = Siae.objects # Get (registered) query parameters filters queryset = self._filter_by_query_params(self.request, queryset) try: return queryset.order_by("id") finally: # Tracking is currently done via user-agent header logger.info( "User-Agent: %s", self.request.headers.get("User-Agent"), ) def _filter_by_query_params(self, request, queryset): params = request.query_params code_insee = params.get(CODE_INSEE_PARAM_NAME) t = f"Les paramètres `{CODE_INSEE_PARAM_NAME}` et `{DISTANCE_FROM_CODE_INSEE_PARAM_NAME}` sont obligatoires." if params.get(DISTANCE_FROM_CODE_INSEE_PARAM_NAME) and code_insee: distance_filter = int( params.get(DISTANCE_FROM_CODE_INSEE_PARAM_NAME)) if distance_filter < 0 or distance_filter > MAX_DISTANCE_RADIUS_KM: raise ValidationError( f"Le paramètre `{DISTANCE_FROM_CODE_INSEE_PARAM_NAME}` doit être compris entre 0 et {MAX_DISTANCE_RADIUS_KM}." # noqa: E501 ) try: city = City.objects.get(code_insee=code_insee) return queryset.within(city.coords, distance_filter) except City.DoesNotExist: # Ensure the error comes from a missing city, which may not be that clear # with get_object_or_404 raise NotFound( f"Pas de ville avec pour code_insee {code_insee}") else: raise ValidationError(t) return queryset
from drf_spectacular.utils import OpenApiParameter, OpenApiExample from drf_spectacular.types import OpenApiTypes # We keep a list of all parameters here, # to make it easier to included them in the doc backers_api_parameters = [] q_param = OpenApiParameter( name='q', type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description="Rechercher par nom." "<br /><br />" "Note : il est possible d'avoir des résultats pertinents avec seulement le début du nom.", examples=[ OpenApiExample('', value=''), OpenApiExample('ademe', value='ademe'), OpenApiExample('conseil régional', value='conseil régional'), OpenApiExample('agenc', value='agenc') ]) backers_api_parameters.append(q_param) has_financed_aids_param = OpenApiParameter( name='has_financed_aids', type=OpenApiTypes.BOOL, location=OpenApiParameter.QUERY, description="Renvoyer seulement les porteurs d'aides avec des aides.", examples=[ OpenApiExample('', value=''), OpenApiExample('true', value=True) ])
class Fixed(self.target_class): @extend_schema( operation_id="get_user", description="Returns the user information for the currently logged on user.", responses={ status.HTTP_200_OK: UserSerializer, status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "No token", value={"detail": "Invalid token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["users"], ) def get(self, *args, **kwargs): pass @extend_schema( operation_id="update_user", description="Updates the user information for the currently logged on user.", request={"application/json": UserSerializer}, responses={ status.HTTP_200_OK: UserSerializer, status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "No token", value={"detail": "Invalid token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["users"], ) def put(self, *args, **kwargs): pass @extend_schema( operation_id="partial_update_user", description="Partially updates the user information for the currently logged on user.", request={"application/json": UserSerializer}, responses={ status.HTTP_200_OK: UserSerializer, status.HTTP_401_UNAUTHORIZED: OpenApiResponse( DetailResponseSerializer, description="Unauthorized", examples=[ OpenApiExample( "No token", value={"detail": "Invalid token."}, status_codes=[f"{status.HTTP_401_UNAUTHORIZED}"], ) ], ), }, tags=["users"], ) def patch(self, *args, **kwargs): pass
class CharacterListAPIView(ListCreateAPIView): queryset = Character.objects.all() serializer_class = CharacterListSerializer @extend_schema(operation_id='Get list of all characters', examples=[ OpenApiExample('Example 1', response_only=True, value=[{ "id": 1, "strength_modifier": 4, "dexterity_modifier": 3, "intelligence_modifier": 2, "constitution_modifier": 0, "wisdom_modifier": -1, "charisma_modifier": -2, "strength_score": 18, "dexterity_score": 17, "intelligence_score": 15, "constitution_score": 10, "wisdom_score": 8, "charisma_score": 7 }]) ]) def get(self, request, *args, **kwargs): """ Get list of all character's information. You could picture this as the list of all character sheets. """ return super().get(request, *args, **kwargs) @extend_schema(operation_id='Create a character', responses={201: CharacterListSerializer}, examples=[ OpenApiExample('Example 1', request_only=True, value={ 'strength_score': 18, 'dexterity_score': 17, 'intelligence_score': 15, 'constitution_score': 10, 'wisdom_score': 8, 'charisma_score': 7 }), OpenApiExample('Example 2', response_only=True, value={ "id": 1, "strength_modifier": 4, "dexterity_modifier": 3, "intelligence_modifier": 2, "constitution_modifier": 0, "wisdom_modifier": -1, "charisma_modifier": -2, "strength_score": 18, "dexterity_score": 17, "intelligence_score": 15, "constitution_score": 10, "wisdom_score": 8, "charisma_score": 7 }) ]) def post(self, request, *args, **kwargs): """ Create a character information. """ return super().post(request, *args, **kwargs)
from dj_rest_auth.serializers import LoginSerializer as OriginalLoginSerializer from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiExample, extend_schema_serializer, extend_schema_field from rest_framework import serializers from rest_framework.reverse import reverse @extend_schema_serializer(examples=[ OpenApiExample('Local development example', value={ 'username': '******', 'password': '******', }, request_only=True), ]) class LoginSerializer(OriginalLoginSerializer ): # remove third optional field from library """ The API serializer for username / password login. """ username = serializers.CharField() password = serializers.CharField() email = None class JWTSerializer(serializers.Serializer): """ The API serializer for getting security information after successful login. """ access_token = serializers.CharField() access_token_verify_url = serializers.SerializerMethodField()
from rest_framework.response import Response from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import ( OpenApiExample, OpenApiParameter, extend_schema, extend_schema_serializer, ) from tests import assert_schema, generate_schema @extend_schema_serializer(examples=[ OpenApiExample( 'Serializer A Example RO', value={"field": 1}, response_only=True, ), OpenApiExample( 'Serializer A Example WO', value={"field": 2}, request_only=True, ), OpenApiExample('Serializer A Example RW', summary='Serializer A Example RW custom summary', value={'field': 3}), OpenApiExample('Serializer A Example RW External', external_value='https://example.com/example_a.txt', media_type='application/x-www-form-urlencoded') ]) class ASerializer(serializers.Serializer):
class OrganizationSCIMMemberDetails(SCIMEndpoint, OrganizationMemberEndpoint): permission_classes = (OrganizationSCIMMemberPermission, ) public = {"GET", "DELETE", "PATCH"} def _delete_member(self, request: Request, organization, member): audit_data = member.get_audit_log_data() if member.is_only_owner(): raise PermissionDenied(detail=ERR_ONLY_OWNER) with transaction.atomic(): AuthIdentity.objects.filter( user=member.user, auth_provider__organization=organization).delete() member.delete() self.create_audit_entry( request=request, organization=organization, target_object=member.id, target_user=member.user, event=AuditLogEntryEvent.MEMBER_REMOVE, data=audit_data, ) def _should_delete_member(self, operation): if operation["op"].lower() == MemberPatchOps.REPLACE: if isinstance(operation["value"], dict) and operation["value"]["active"] is False: # how okta sets active to false return True elif operation["path"] == "active" and operation["value"] is False: # how other idps set active to false return True return False @extend_schema( operation_id="Query an Individual Organization Member", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIM_PARAMS.MEMBER_ID], request=None, responses={ 200: OrganizationMemberSCIMSerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "Successful response", value={ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "102", "userName": "******", "emails": [{ "primary": True, "value": "*****@*****.**", "type": "work" }], "name": { "familyName": "N/A", "givenName": "N/A" }, "active": True, "meta": { "resourceType": "User" }, }, status_codes=["200"], ), ], ) def get(self, request: Request, organization, member) -> Response: """ Query an individual organization member with a SCIM User GET Request. - The `name` object will contain fields `firstName` and `lastName` with the values of `N/A`. Sentry's SCIM API does not currently support these fields but returns them for compatibility purposes. """ context = serialize( member, serializer=_scim_member_serializer_with_expansion(organization), ) return Response(context) @extend_schema( operation_id="Update an Organization Member's Attributes", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIM_PARAMS.MEMBER_ID], request=SCIMPatchRequestSerializer, responses={ 204: RESPONSE_SUCCESS, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "Set member inactive", value={ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "value": { "active": False } }], }, status_codes=["204"], ), ], ) def patch(self, request: Request, organization, member): """ Update an organization member's attributes with a SCIM PATCH Request. The only supported attribute is `active`. After setting `active` to false Sentry will permanently delete the Organization Member. """ serializer = SCIMPatchRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( { "schemas": SCIM_API_ERROR, "detail": json.dumps(serializer.errors) }, status=400) result = serializer.validated_data for operation in result["operations"]: # we only support setting active to False which deletes the orgmember if self._should_delete_member(operation): self._delete_member(request, organization, member) return Response(status=204) else: return Response(SCIM_400_INVALID_PATCH, status=400) context = serialize( member, serializer=_scim_member_serializer_with_expansion(organization), ) return Response(context) @extend_schema( operation_id="Delete an Organization Member via SCIM", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIM_PARAMS.MEMBER_ID], request=None, responses={ 204: RESPONSE_SUCCESS, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, ) def delete(self, request: Request, organization, member) -> Response: """ Delete an organization member with a SCIM User DELETE Request. """ self._delete_member(request, organization, member) return Response(status=204)
is_verified=True) return Response({"email": "Successfully activated"}, status=status.HTTP_200_OK) except jwt.exceptions.ExpiredSignatureError as err: return Response({"error": "Activation Expired"}, status=status.HTTP_400_BAD_REQUEST) except jwt.DecodeError as err: return Response({"error": "Invalid Token"}, status=status.HTTP_400_BAD_REQUEST) @extend_schema(tags=["Register"], summary="login 하자", examples=[ OpenApiExample(request_only=True, name="sample1", value={ "email": "*****@*****.**", "password": "******" }), ]) class LoginAPIView(generics.GenericAPIView): serializer_class = LoginSerializer def post(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) return Response(serializer.data, status=status.HTTP_200_OK)
from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_view, extend_schema, OpenApiParameter, extend_schema_serializer, \ OpenApiExample from rest_framework import serializers, generics from kubeportal.models.webapplication import WebApplication @extend_schema_serializer( examples=[ OpenApiExample( '', value={ 'link_name': 'Grafana', 'link_url': 'https://monitoring.example.com', 'category': 'MONITORING' }, response_only=True ), ] ) class WebAppSerializer(serializers.ModelSerializer): class Meta: model = WebApplication fields = ['link_name', 'link_url', 'category'] def to_representation(self, data): data = super(WebAppSerializer, self).to_representation(data) link = data["link_url"] try: ns = self.context["request"].user.service_account.namespace.name
class OrganizationStatsEndpointV2(OrganizationEventsEndpointBase): enforce_rate_limit = True rate_limits = { "GET": { RateLimitCategory.IP: RateLimit(20, 1), RateLimitCategory.USER: RateLimit(20, 1), RateLimitCategory.ORGANIZATION: RateLimit(20, 1), } } public = {"GET"} @extend_schema( operation_id="Retrieve Event Counts for an Organization (v2)", parameters=[GLOBAL_PARAMS.ORG_SLUG, OrgStatsQueryParamsSerializer], request=None, responses={ 200: inline_sentry_response_serializer("Outcomes Response", StatsApiResponse), 401: RESPONSE_UNAUTHORIZED, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "Successful response", value={ "start": "2022-02-14T19:00:00Z", "end": "2022-02-28T18:03:00Z", "intervals": ["2022-02-28T00:00:00Z"], "groups": [{ "by": { "outcome": "invalid" }, "totals": { "sum(quantity)": 165665 }, "series": { "sum(quantity)": [165665] }, }], }, status_codes=["200"], ), ], ) def get(self, request: Request, organization) -> Response: """ Query event counts for your Organization. Select a field, define a date range, and group or filter by columns. """ with self.handle_query_errors(): with sentry_sdk.start_span(op="outcomes.endpoint", description="build_outcomes_query"): query = self.build_outcomes_query( request, organization, ) with sentry_sdk.start_span(op="outcomes.endpoint", description="run_outcomes_query"): result_totals = run_outcomes_query_totals(query) result_timeseries = (None if "project_id" in query.query_groupby else run_outcomes_query_timeseries(query)) with sentry_sdk.start_span(op="outcomes.endpoint", description="massage_outcomes_result"): result = massage_outcomes_result(query, result_totals, result_timeseries) return Response(result, status=200) def build_outcomes_query(self, request: Request, organization): params = {"organization_id": organization.id} project_ids = self._get_projects_for_orgstats_query( request, organization) if project_ids: params["project_id"] = project_ids return QueryDefinition(request.GET, params) def _get_projects_for_orgstats_query(self, request: Request, organization): # look at the raw project_id filter passed in, if its empty # and project_id is not in groupBy filter, treat it as an # org wide query and don't pass project_id in to QueryDefinition req_proj_ids = self.get_requested_project_ids_unchecked(request) if self._is_org_total_query(request, req_proj_ids): return None else: projects = self.get_projects(request, organization, project_ids=req_proj_ids) if not projects: raise NoProjects("No projects available") return [p.id for p in projects] def _is_org_total_query(self, request: Request, project_ids): return all([ not project_ids or project_ids == ALL_ACCESS_PROJECTS, "project" not in request.GET.get("groupBy", []), ]) @contextmanager def handle_query_errors(self): try: # TODO: this context manager should be decoupled from `OrganizationEventsEndpointBase`? with super().handle_query_errors(): yield except (InvalidField, NoProjects, InvalidParams, InvalidQuery, InvalidParamsApi) as error: raise ParseError(detail=str(error))
# We keep a list of all parameters here, # to make it easier to included them in the doc perimeters_api_parameters = [] q_param = OpenApiParameter( name='q', type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description="Rechercher par nom." "<br /><br />" "Note : il est possible d'avoir des résultats pertinents avec seulement le début du nom, \ ou un nom légerement erroné.", examples=[ OpenApiExample('', value=''), OpenApiExample('lyon', value='lyon'), OpenApiExample('par', value='par'), OpenApiExample('grenble', value='grenble') ]) perimeters_api_parameters.append(q_param) scale_param = OpenApiParameter( name='scale', type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description="Filtrer par l'échelle." "<br /><br />" "Voir `/api/perimeters/scales/` pour la liste complète.", enum=[id for (weight, id, name) in Perimeter.SCALES_TUPLE], examples=[OpenApiExample('', value='')] +
class OrganizationSCIMTeamIndex(SCIMEndpoint, OrganizationTeamsEndpoint): permission_classes = (OrganizationSCIMTeamPermission,) public = {"GET", "POST"} def team_serializer_for_post(self): return TeamSCIMSerializer(expand=["members"]) def should_add_creator_to_team(self, request: Request): return False @extend_schema( operation_id="List an Organization's Paginated Teams", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIMQueryParamSerializer], request=None, responses={ 200: scim_response_envelope( "SCIMTeamIndexResponse", OrganizationTeamSCIMSerializerResponse ), 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "listGroups", value={ "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "totalResults": 1, "startIndex": 1, "itemsPerPage": 1, "Resources": [ { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "23232", "displayName": "test-scimv2", "members": [], "meta": {"resourceType": "Group"}, } ], }, status_codes=["200"], ), ], ) def get(self, request: Request, organization) -> Response: """ Returns a paginated list of teams bound to a organization with a SCIM Groups GET Request. - Note that the members field will only contain up to 10000 members. """ query_params = self.get_query_parameters(request) queryset = Team.objects.filter( organization=organization, status=TeamStatus.VISIBLE ).order_by("slug") if query_params["filter"]: queryset = queryset.filter(slug__iexact=slugify(query_params["filter"])) def data_fn(offset, limit): return list(queryset[offset : offset + limit]) def on_results(results): results = serialize( results, None, TeamSCIMSerializer(expand=_team_expand(query_params["excluded_attributes"])), ) return self.list_api_format(results, queryset.count(), query_params["start_index"]) return self.paginate( request=request, on_results=on_results, paginator=GenericOffsetPaginator(data_fn=data_fn), default_per_page=query_params["count"], queryset=queryset, cursor_cls=SCIMCursor, ) @extend_schema( operation_id="Provision a New Team", parameters=[GLOBAL_PARAMS.ORG_SLUG], request=inline_serializer( "SCIMTeamRequestBody", fields={ "schemas": serializers.ListField(serializers.CharField()), "displayName": serializers.CharField(), "members": serializers.ListField(serializers.IntegerField()), }, ), responses={ 201: TeamSCIMSerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "provisionTeam", response_only=True, value={ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Test SCIMv2", "members": [], "meta": {"resourceType": "Group"}, "id": "123", }, status_codes=["201"], ), ], ) def post(self, request: Request, organization) -> Response: """ Create a new team bound to an organization via a SCIM Groups POST Request. Note that teams are always created with an empty member set. The endpoint will also do a normalization of uppercase / spaces to lowercase and dashes. """ # shim displayName from SCIM api in order to work with # our regular team index POST request.data.update( {"name": request.data["displayName"], "slug": slugify(request.data["displayName"])} ), return super().post(request, organization)
from django.utils.decorators import method_decorator from drf_spectacular.extensions import OpenApiFilterExtension, OpenApiViewExtension from drf_spectacular.openapi import AutoSchema from drf_spectacular.plumbing import get_doc from drf_spectacular.utils import OpenApiExample, extend_schema from . import app_settings wca_login_request_example = OpenApiExample( "WCA login request example", value={ "code": "OTQ5HFpRcpwBJPxGZgwc0Dc5LpnTUXxVVNHxe2QDdEl", "callback_url": app_settings.WCA_DEFAULT_CALLBACK_URL, }, request_only=True, ) wca_login_response_example = OpenApiExample( "WCA login response example", value={"key": "cjcsu60bc8fi4hw9s4lbhgnbtc4ls1xlyga99qe0"}, response_only=True, ) user_retrieve_example = OpenApiExample( "User retrieve example", value={ "first_name": "Juan", "last_name": "dela Cruz", "wca_id": "2021DELA01", "region": "NCR", "region_updated_at": "2021-04-24T03:14:50.069Z", "created_at": "2021-04-24T03:14:50.069Z",
class OrganizationSCIMTeamDetails(SCIMEndpoint, TeamDetailsEndpoint): permission_classes = (OrganizationSCIMTeamPermission,) public = {"GET", "PATCH"} def convert_args(self, request: Request, organization_slug, team_id, *args, **kwargs): args, kwargs = super().convert_args(request, organization_slug) try: kwargs["team"] = self._get_team(kwargs["organization"], team_id) except Team.DoesNotExist: raise ResourceDoesNotExist(detail=SCIM_404_GROUP_RES) return (args, kwargs) def _get_team(self, organization, team_id): team = ( Team.objects.filter(organization=organization, id=team_id) .select_related("organization") .get() ) if team.status != TeamStatus.VISIBLE: raise Team.DoesNotExist return team @extend_schema( operation_id="Query an Individual Team", parameters=[SCIM_PARAMS.TEAM_ID, GLOBAL_PARAMS.ORG_SLUG], request=None, responses={ 200: TeamSCIMSerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "Successful response", value={ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "23232", "displayName": "test-scimv2", "members": [], "meta": {"resourceType": "Group"}, }, ), ], ) def get(self, request: Request, organization, team) -> Response: """ Query an individual team with a SCIM Group GET Request. - Note that the members field will only contain up to 10000 members. """ query_params = self.get_query_parameters(request) context = serialize( team, serializer=TeamSCIMSerializer(expand=_team_expand(query_params["excluded_attributes"])), ) return Response(context) def _add_members_operation(self, request: Request, operation, team): for member in operation["value"]: member = OrganizationMember.objects.get( organization=team.organization, id=member["value"] ) if OrganizationMemberTeam.objects.filter(team=team, organizationmember=member).exists(): # if a member already belongs to a team, do nothing continue with transaction.atomic(): omt = OrganizationMemberTeam.objects.create(team=team, organizationmember=member) self.create_audit_entry( request=request, organization=team.organization, target_object=omt.id, target_user=member.user, event=AuditLogEntryEvent.MEMBER_JOIN_TEAM, data=omt.get_audit_log_data(), ) def _remove_members_operation(self, request: Request, member_id, team): member = OrganizationMember.objects.get(organization=team.organization, id=member_id) with transaction.atomic(): try: omt = OrganizationMemberTeam.objects.get(team=team, organizationmember=member) except OrganizationMemberTeam.DoesNotExist: return self.create_audit_entry( request=request, organization=team.organization, target_object=omt.id, target_user=member.user, event=AuditLogEntryEvent.MEMBER_LEAVE_TEAM, data=omt.get_audit_log_data(), ) omt.delete() def _rename_team_operation(self, request: Request, new_name, team): serializer = TeamSerializer( team, data={"name": new_name, "slug": slugify(new_name)}, partial=True, ) if serializer.is_valid(): team = serializer.save() self.create_audit_entry( request=request, organization=team.organization, target_object=team.id, event=AuditLogEntryEvent.TEAM_EDIT, data=team.get_audit_log_data(), ) @extend_schema( operation_id="Update a Team's Attributes", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIM_PARAMS.TEAM_ID], request=SCIMTeamPatchRequestSerializer, responses={ 204: RESPONSE_SUCCESS, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, ) def patch(self, request: Request, organization, team): """ A SCIM Group PATCH request takes a series of operations to perform on a team. It does them sequentially and if any of them fail no operations should go through. The operations are add members, remove members, replace members, and rename team. Update a team's attributes with a SCIM Group PATCH Request. Valid Operations are: * Renaming a team: ```json { "op": "replace", "value": { "id": 23, "displayName": "newName" } } ``` * Adding a member to a team: ```json { "op": "add", "path": "members", "value": [ { "value": 23, "display": "*****@*****.**" } ] } ``` * Removing a member from a team: ```json { "op": "remove", "path": "members[value eq \"23\"]" } ``` * Replacing an entire member set of a team: ```json { "op": "replace", "path": "members", "value": [ { "value": 23, "display": "*****@*****.**" }, { "value": 24, "display": "*****@*****.**" } ] } ``` """ operations = request.data.get("Operations", []) if len(operations) > 100: return Response(SCIM_400_TOO_MANY_PATCH_OPS_ERROR, status=400) try: with transaction.atomic(): for operation in operations: op = operation["op"].lower() if op == TeamPatchOps.ADD and operation["path"] == "members": self._add_members_operation(request, operation, team) elif op == TeamPatchOps.REMOVE and "members" in operation["path"]: self._remove_members_operation( request, self._get_member_id_for_remove_op(operation), team ) elif op == TeamPatchOps.REPLACE: path = operation.get("path") if path == "members": # delete all the current team members # and replace with the ones in the operation list with transaction.atomic(): queryset = OrganizationMemberTeam.objects.filter(team_id=team.id) queryset.delete() self._add_members_operation(request, operation, team) # azure and okta handle team name change operation differently elif path is None: # for okta self._rename_team_operation( request, operation["value"]["displayName"], team ) elif path == "displayName": # for azure self._rename_team_operation(request, operation["value"], team) else: return Response(SCIM_400_UNSUPPORTED_ATTRIBUTE, status=400) except OrganizationMember.DoesNotExist: raise ResourceDoesNotExist(detail=SCIM_404_USER_RES) except IntegrityError as e: sentry_sdk.capture_exception(e) return Response(SCIM_400_INTEGRITY_ERROR, status=400) return self.respond(status=204) @extend_schema( operation_id="Delete an Individual Team", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIM_PARAMS.TEAM_ID], request=None, responses={ 204: RESPONSE_SUCCESS, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, ) def delete(self, request: Request, organization, team) -> Response: """ Delete a team with a SCIM Group DELETE Request. """ return super().delete(request, team) def put(self, request: Request, organization, team) -> Response: # override parent's put since we don't have puts # in SCIM Team routes return self.http_method_not_allowed(request) def _get_member_id_for_remove_op(self, operation): if "value" in operation: # azure sends member ids in this format under the key 'value' return operation["value"][0]["value"] try: # grab the filter out of the brackets of the string that looks # like so: members[value eq "123124"] regex_search = re.search(r"\[(.*?)\]", operation["path"]) if regex_search is None: raise SCIMFilterError filter_path = regex_search.groups()[0] return parse_filter_conditions(filter_path) except SCIMFilterError: raise ParseError(detail=SCIM_400_INVALID_FILTER)
def get_override_parameters(self): """Expose the DSO-specific HTTP headers in all API methods.""" extra = [ OpenApiParameter( name=api_settings.URL_FORMAT_OVERRIDE, type={ "type": "string", "enum": [ renderer.format for renderer in self.view.renderer_classes if renderer.format != "api" # Exclude browser view ], }, location=OpenApiParameter.QUERY, description="Select the export format", required=False, ), ] if isinstance(self.view, DSOViewMixin): extra += [ OpenApiParameter( "Accept-Crs", type=OpenApiTypes.STR, location=OpenApiParameter.HEADER, description="Accept-Crs header for Geo queries", required=False, ), OpenApiParameter( "Content-Crs", type=OpenApiTypes.STR, location=OpenApiParameter.HEADER, description="Content-Crs header for Geo queries", required=False, ), ] # Expose expand parameters too. if issubclass(self.view.serializer_class, ExpandableSerializer): embeds = get_all_embedded_fields_by_name( self.view.serializer_class) examples = [] if embeds: examples = [ OpenApiExample( name=dotted_name, value=dotted_name, description=self._get_expand_description(field), ) for dotted_name, field in sorted(embeds.items()) ] examples.append( OpenApiExample( name="All Values", value=",".join(sorted(embeds.keys())), description= "Expand all fields, identical to only using _expand=true.", )) extra += [ OpenApiParameter( "_expand", type=OpenApiTypes.BOOL, location=OpenApiParameter.QUERY, description="Allow to expand relations.", required=False, ), OpenApiParameter( "_expandScope", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description= "Comma separated list of named relations to expand.", required=False, examples=examples, ), ] return extra
class ExampleTestWithExtendedViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): serializer_class = ASerializer @extend_schema( request=ASerializer, responses={ 201: BSerializer, 400: OpenApiTypes.OBJECT, 403: OpenApiTypes.OBJECT, }, examples=[ OpenApiExample( 'Create Example RO', value={'field': 11}, response_only=True, ), OpenApiExample( 'Create Example WO', value={'field': 22}, request_only=True, ), OpenApiExample( 'Create Example RW', value={'field': 33}, ), OpenApiExample('Create Error 403 Example', value={'field': 'error'}, response_only=True, status_codes=['403']), ], ) def create(self, request, *args, **kwargs): super().create(request, *args, **kwargs) # pragma: no cover @extend_schema( parameters=[ OpenApiParameter( name="artist", description="Filter by artist", required=False, type=str, examples=[ OpenApiExample( "Artist Query Example 1", value="prince", description="description for artist query example 1"), OpenApiExample( "Artist Query Example 2", value="miles davis", description="description for artist query example 2") ]), ], responses=CSerializer, ) def list(self, request): return Response() # pragma: no cover @action(detail=False, methods=['GET']) def raw_action(self, request): return Response() # pragma: no cover @extend_schema(responses=BSerializer) @action(detail=False, methods=['POST']) def override_extend_schema_action(self, request): return Response() # pragma: no cover
class OrganizationSCIMMemberIndex(SCIMEndpoint): permission_classes = (OrganizationSCIMMemberPermission, ) public = {"GET", "POST"} @extend_schema( operation_id="List an Organization's Members", parameters=[GLOBAL_PARAMS.ORG_SLUG, SCIMQueryParamSerializer], request=None, responses={ 200: scim_response_envelope("SCIMMemberIndexResponse", OrganizationMemberSCIMSerializerResponse), 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "List an Organization's Members", value={ "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "totalResults": 1, "startIndex": 1, "itemsPerPage": 1, "Resources": [{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "102", "userName": "******", "emails": [{ "primary": True, "value": "*****@*****.**", "type": "work" }], "name": { "familyName": "N/A", "givenName": "N/A" }, "active": True, "meta": { "resourceType": "User" }, }], }, status_codes=["200"], ), ], ) def get(self, request: Request, organization) -> Response: """ Returns a paginated list of members bound to a organization with a SCIM Users GET Request. """ # note that SCIM doesn't care about changing results as they're queried query_params = self.get_query_parameters(request) queryset = (OrganizationMember.objects.filter( Q(invite_status=InviteStatus.APPROVED.value), Q(user__is_active=True) | Q(user__isnull=True), organization=organization, ).select_related("user").order_by("email", "user__email")) if query_params["filter"]: queryset = queryset.filter( Q(email__iexact=query_params["filter"]) | Q(user__email__iexact=query_params["filter"]) ) # not including secondary email vals (dups, etc.) def data_fn(offset, limit): return list(queryset[offset:offset + limit]) def on_results(results): results = serialize( results, None, _scim_member_serializer_with_expansion(organization), ) return self.list_api_format(results, queryset.count(), query_params["start_index"]) return self.paginate( request=request, on_results=on_results, paginator=GenericOffsetPaginator(data_fn=data_fn), default_per_page=query_params["count"], queryset=queryset, cursor_cls=SCIMCursor, ) @extend_schema( operation_id="Provision a New Organization Member", parameters=[GLOBAL_PARAMS.ORG_SLUG], request=inline_serializer( "SCIMMemberProvision", fields={"userName": serializers.EmailField()}), responses={ 201: OrganizationMemberSCIMSerializer, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOTFOUND, }, examples=[ # TODO: see if this can go on serializer object instead OpenApiExample( "Provision new member", response_only=True, value={ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "242", "userName": "******", "emails": [{ "primary": True, "value": "*****@*****.**", "type": "work" }], "active": True, "name": { "familyName": "N/A", "givenName": "N/A" }, "meta": { "resourceType": "User" }, }, status_codes=["201"], ), ], ) def post(self, request: Request, organization) -> Response: """ Create a new Organization Member via a SCIM Users POST Request. - `userName` should be set to the SAML field used for email, and active should be set to `true`. - Sentry's SCIM API doesn't currently support setting users to inactive, and the member will be deleted if inactive is set to `false`. - The API also does not support setting secondary emails. """ serializer = OrganizationMemberSerializer( data={ "email": request.data.get("userName"), "role": roles.get(organization.default_role).id, }, context={ "organization": organization, "allowed_roles": [roles.get(organization.default_role)], "allow_existing_invite_request": True, }, ) if not serializer.is_valid(): if "email" in serializer.errors and any( ("is already a member" in error) for error in serializer.errors["email"]): # we include conflict logic in the serializer, check to see if that was # our error and if so, return a 409 so the scim IDP knows how to handle raise ConflictError(detail=SCIM_409_USER_EXISTS) return Response(serializer.errors, status=400) result = serializer.validated_data with transaction.atomic(): member = OrganizationMember( organization=organization, email=result["email"], role=result["role"], inviter=request.user, ) # TODO: are invite tokens needed for SAML orgs? if settings.SENTRY_ENABLE_INVITES: member.token = member.generate_token() member.save() self.create_audit_entry( request=request, organization_id=organization.id, target_object=member.id, data=member.get_audit_log_data(), event=AuditLogEntryEvent.MEMBER_INVITE if settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD, ) if settings.SENTRY_ENABLE_INVITES and result.get("sendInvite"): member.send_invite_email() member_invited.send_robust( member=member, user=request.user, sender=self, referrer=request.data.get("referrer"), ) context = serialize( member, serializer=_scim_member_serializer_with_expansion(organization), ) return Response(context, status=201)
from aids.constants import ( AUDIENCES_ALL, AID_TYPE_CHOICES, FINANCIAL_AIDS, TECHNICAL_AIDS, OTHER_AIDS) # We keep a list of all parameters here, # to make it easier to included them in the doc aids_api_parameters = [] text = OpenApiParameter( name='text', type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description="Recherche textuelle.", examples=[ OpenApiExample('', value=''), OpenApiExample('velo', value='velo'), OpenApiExample('piste OU velo', value='piste+velo'), OpenApiExample('piste ET velo', value='piste velo') ]) aids_api_parameters.append(text) targeted_audiences = OpenApiParameter( name='targeted_audiences', type=OpenApiTypes.STR, location=OpenApiParameter.QUERY, description="La structure pour laquelle vous recherchez des aides." "<br /><br />" "Voir aussi `/api/aids/audiences/` pour la liste complète.", enum=[id for (id, key) in AUDIENCES_ALL], examples=[OpenApiExample('', value='')] +