def post(self, request): form = FeedForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) if not has_object_permission('check_catalog_manage', request.user, form.cleaned_data['catalog_id']): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) if Feed.objects.filter( catalog=form.cleaned_data['catalog_id'], url_name=form.cleaned_data['url_name']).exists(): raise ProblemDetailException( request, _("Feed with same url_name already exists in same catalog"), status=HTTPStatus.CONFLICT) feed = Feed(creator=request.user) form.populate(feed) feed.save() if 'entries' in form.cleaned_data.keys(): feed.entries.add(*form.cleaned_data['entries']) if 'parents' in form.cleaned_data.keys(): feed.parents.add(*form.cleaned_data['parents']) return SingleResponse(request, feed, serializer=FeedSerializer.Base, status=HTTPStatus.CREATED)
def post(self, request): form = CatalogForm.create_from_request(request) if not request.user.has_perm('core.add_catalog'): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) if not form.is_valid(): raise ValidationException(request, form) if Catalog.objects.filter( url_name=form.cleaned_data['url_name']).exists(): raise ProblemDetailException( request, title=_('Catalog url_name already taken'), status=HTTPStatus.CONFLICT) service = CatalogService() catalog = service.populate(catalog=Catalog(creator=request.user), form=form) if not catalog.users.contains(request.user): UserCatalog.objects.create(catalog=catalog, user=request.user, mode=UserCatalog.Mode.MANAGE) return SingleResponse(request, catalog, serializer=CatalogSerializer.Detailed, status=HTTPStatus.CREATED)
def post(self, request): form = CreateAuthorForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) if not has_object_permission('check_catalog_write', request.user, form.cleaned_data['catalog_id']): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) if Author.objects.filter( catalog=form.cleaned_data['catalog_id'], name=form.cleaned_data['name'], surname=form.cleaned_data['surname']).exists(): raise ProblemDetailException( request, _("Author already exists in the catalog"), status=HTTPStatus.CONFLICT) author = Author() form.populate(author) author.save() return SingleResponse(request, author, serializer=AuthorSerializer.Detailed, status=HTTPStatus.CREATED)
def post(self, request): form = CreateUserForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) if not request.user.has_perm('core.add_user'): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) if User.objects.filter(email=form.cleaned_data['email']).exists(): raise ProblemDetailException( request, _("User with same email already exists"), status=HTTPStatus.CONFLICT) user = User() form.populate(user) user.set_password(form.cleaned_data['password']) user.save() return SingleResponse(request, user, serializer=UserSerializer.Base, status=HTTPStatus.CREATED)
def _get_entry(request, catalog_id: uuid.UUID, entry_id: uuid.UUID, checker: str = 'check_entry_manage') -> Entry: try: entry = Entry.objects.get(pk=entry_id, catalog_id=catalog_id) except Entry.DoesNotExist: raise ProblemDetailException(request, _("Entry not found"), status=HTTPStatus.NOT_FOUND) if not has_object_permission(checker, request.user, entry): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return entry
def post(self, request): form = RefreshTokenForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) try: claims = JWTFactory.decode(form.cleaned_data['refresh']) except JoseError as e: raise ProblemDetailException(request, _('Invalid token.'), status=HTTPStatus.UNAUTHORIZED, previous=e) redis = Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DATABASE) if not redis.exists(f"refresh_token:{claims['jti']}"): raise UnauthorizedException(request) access_token = JWTFactory(claims['sub']).access() return SingleResponse(request, {'access_token': access_token}, status=HTTPStatus.OK)
def get(self, request, entry_id: uuid.UUID): try: entry = Entry.objects.get(pk=entry_id, image__isnull=False) except Entry.DoesNotExist: raise ProblemDetailException(request, _("Entry image not found"), status=HTTPStatus.NOT_FOUND) return FileResponse(entry.image, as_attachment=True, filename=entry.title)
def put(self, request, feed_id: UUID): feed = self._get_feed(request, feed_id) form = FeedForm.create_from_request(request) form['parents'].queryset = form['parents'].queryset.exclude(pk=feed.pk) if not form.is_valid(): raise ValidationException(request, form) if Feed.objects.filter(catalog=form.cleaned_data['catalog_id'], url_name=form.cleaned_data['url_name']).exclude( pk=feed.id).exists(): raise ProblemDetailException( request, _("Feed with same url_name already exists in same catalog"), status=HTTPStatus.CONFLICT) form.populate(feed) feed.save() if feed.kind == Feed.FeedKind.ACQUISITION and 'entries' in form.cleaned_data.keys( ): feed.entries.clear() feed.entries.add(*form.cleaned_data['entries']) if feed.kind == Feed.FeedKind.NAVIGATION and 'parents' in form.cleaned_data.keys( ): feed.parents.clear() feed.parents.add(*form.cleaned_data['parents']) return SingleResponse(request, feed, serializer=FeedSerializer.Base)
def _get_feed(request, feed_id: UUID) -> Feed: try: feed = Feed.objects.select_related('catalog').get(pk=feed_id) except Feed.DoesNotExist as e: raise ProblemDetailException(request, _("Feed not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not has_object_permission('check_catalog_manage', request.user, feed.catalog): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return feed
def dispatch(self, request, *args, **kwargs): try: self.catalog = Catalog.objects.get(url_name=kwargs['catalog_name']) except Catalog.DoesNotExist: raise ProblemDetailException(request, _("Catalog not found"), status=HTTPStatus.NOT_FOUND) except KeyError as e: raise ProblemDetailException(request, _("Internal server error"), status=HTTPStatus.NOT_FOUND, previous=e) if request.method not in self.UNSECURED_METHODS and not self.catalog.is_public: self._authenticate(request) return View.dispatch(self, request, *args, **kwargs)
def delete(self, request, api_key_id: UUID): try: api_key = ApiKey.objects.get(pk=api_key_id) except ApiKey.DoesNotExist as e: raise ProblemDetailException(request, _("ApiKey not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not request.user.is_superuser and api_key.user_id != request.user.id: raise ProblemDetailException(request, _("ApiKey not found"), status=HTTPStatus.NOT_FOUND) api_key.hard_delete() return SingleResponse(request, status=HTTPStatus.NO_CONTENT)
def _get_author(request, author_id: UUID, checker: str = 'check_catalog_manage') -> Author: try: author = Author.objects.select_related('catalog').get(pk=author_id) except Author.DoesNotExist as e: raise ProblemDetailException(request, _("Author not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not has_object_permission(checker, request.user, author.catalog): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return author
def _get_catalog(request, catalog_id: UUID, checker: str = 'check_catalog_manage') -> Catalog: try: catalog = Catalog.objects.get(pk=catalog_id) except Catalog.DoesNotExist as e: raise ProblemDetailException(request, _("Catalog not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not has_object_permission(checker, request.user, catalog): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return catalog
def _get_acquisition(request, acquisition_id: UUID, checker: str = 'check_catalog_manage') -> Acquisition: try: acquisition = Acquisition.objects.select_related( 'entry__catalog').get(pk=acquisition_id) except Acquisition.DoesNotExist: raise ProblemDetailException(request, _("Acquisition not found"), status=HTTPStatus.NOT_FOUND) if not has_object_permission(checker, request.user, acquisition.entry.catalog): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return acquisition
def _get_user(request, user_id: UUID, perm_test: Optional[Callable] = None) -> User: try: user = User.objects.get(pk=user_id) except User.DoesNotExist as e: raise ProblemDetailException(request, _("User not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not perm_test: perm_test = lambda: True if not (user.id == request.user.id or perm_test()): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) return user
def get(self, request, catalog_name: str, feed_name: str = None): try: catalog = Catalog.objects.get(url_name=catalog_name) except Catalog.DoesNotExist as e: raise ProblemDetailException(request, _("Feed not found"), status=HTTPStatus.NOT_FOUND, previous=e) tags = { 'short_name': catalog.title, 'description': 'OPDS catalog', 'filter': EntryFilter, 'url': f"{settings.BASE_URL}" f"{reverse('root', kwargs={'catalog_name': catalog.url_name})}" f"?{EntryFilter.template()}" } if feed_name: try: feed = Feed.objects.get(catalog=catalog, url_name=feed_name) except Feed.DoesNotExist as e: raise ProblemDetailException(request, _("Feed not found"), status=HTTPStatus.NOT_FOUND, previous=e) tags['short_name'] = f"{feed.title} - {catalog.title}" tags['description'] = f"{feed.title} feed" tags['url'] = f"{settings.BASE_URL}" \ f"{reverse('feed', kwargs={'catalog_name': catalog.url_name, 'feed_name': feed.url_name})}" \ f"?{EntryFilter.template()}" return render(request, 'opds/search.xml', tags, content_type='application/opensearchdescription+xml')
def post(self, request, catalog_id: uuid.UUID): try: catalog = Catalog.objects.get(pk=catalog_id) except Catalog.DoesNotExist as e: raise ProblemDetailException(request, _("Catalog not found"), status=HTTPStatus.NOT_FOUND, previous=e) if not has_object_permission('check_catalog_write', request.user, catalog): raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) form = EntryForm.create_from_request(request) form.fields['category_ids'].queryset = form.fields['category_ids'].queryset.filter(catalog=catalog) form.fields['author_id'].queryset = form.fields['author_id'].queryset.filter(catalog=catalog) if not form.is_valid(): raise ValidationException(request, form) entry = Entry(creator=request.user, catalog=catalog) service = EntryService(catalog, request.user) service.populate(entry, form) return SingleResponse(request, entry, serializer=EntrySerializer.Detailed, status=HTTPStatus.CREATED)
def authenticate(self, request, bearer=None, **kwargs): try: claims = JWTFactory.decode(bearer) except JoseError as e: raise ProblemDetailException(request, _('Invalid token.'), status=HTTPStatus.UNAUTHORIZED, previous=e) if claims['type'] == 'api_key': try: api_key = ApiKey.objects.get(pk=claims['jti'], is_active=True) except (ApiKey.DoesNotExist, ValidationError): raise ProblemDetailException(request, _('Invalid api key.'), status=HTTPStatus.UNAUTHORIZED) api_key.last_seen_at = timezone.now() api_key.save() setattr(request, 'api_key', api_key) user = api_key.user elif claims['type'] == 'access': try: user = User.objects.get(pk=claims['sub']) except User.DoesNotExist: raise ProblemDetailException(request, _('Inactive user.'), status=HTTPStatus.FORBIDDEN) else: raise ProblemDetailException(request, _('Invalid token'), status=HTTPStatus.UNAUTHORIZED) if not self.user_can_authenticate(user): raise ProblemDetailException(request, _('Inactive user.'), status=HTTPStatus.FORBIDDEN) return user
def __init__( self, request, qs, ordering: Ordering = None, **kwargs ): kwargs.setdefault('content_type', 'application/json') # Ordering ordering = ordering if ordering else Ordering.create_from_request(request) qs = qs.order_by(*ordering.columns) # Pagination paginate = request.GET.get('paginate', 'true') == 'true' if paginate: limit = int(request.GET.get('limit', settings.PAGINATION['DEFAULT_LIMIT'])) page = int(request.GET.get('page', 1)) paginator = Paginator(qs, limit) try: paginator.validate_number(page) except EmptyPage as e: raise ProblemDetailException( request, title=_('Page not found'), status=HTTPStatus.NOT_FOUND, previous=e, detail_type='out_of_range', detail=_('That page contains no results') ) items = paginator.get_page(page) num_pages = paginator.num_pages total = paginator.count else: limit = None page = 1 items = qs num_pages = 1 total = qs.count() data = { 'items': items, 'metadata': { 'page': page, 'limit': limit, 'pages': num_pages, 'total': total } } super().__init__(request, data, **kwargs)
def authenticate(self, request, basic=None, **kwargs): auth_params = base64.b64decode(basic).decode().split(':') # TODO: use auth sources user = super().authenticate(request, username=auth_params[0], password=auth_params[1]) if not user: raise ProblemDetailException( request, _('Invalid credentials'), status=HTTPStatus.UNAUTHORIZED, extra_headers=( ('WWW-Authenticate', f'Bearer realm="{slugify(settings.INSTANCE_NAME)}"'), )) return user
def get(self, request, catalog_name: str, entry_id: UUID): try: entry = Entry.objects.get(pk=entry_id, catalog__url_name=catalog_name) except Entry.DoesNotExist as e: raise ProblemDetailException(request, _("Entry not found"), status=HTTPStatus.NOT_FOUND, previous=e) return render( request, "opds/_partials/entry.xml", { 'entry': entry, 'is_complete': True }, content_type='application/atom+xml;type=entry;profile=opds-catalog' )
def get(self, request, catalog_name: str, feed_name: str): try: feed = Feed.objects.get(catalog=self.catalog, url_name=feed_name) except Feed.DoesNotExist: raise ProblemDetailException(request, _("Feed not found"), status=HTTPStatus.NOT_FOUND) entry_filter = EntryFilter(request.GET, queryset=Entry.objects.filter(feeds=feed), request=request) try: updated_at = entry_filter.qs.latest('updated_at').updated_at except Entry.DoesNotExist: updated_at = self.catalog.updated_at return render(request, 'opds/feeds/feed.xml', { 'feed': feed, 'updated_at': updated_at, 'catalog': self.catalog, 'entry_filter': entry_filter }, content_type=f'application/atom+xml;profile=opds-catalog;kind={feed.kind}')
def post(self, request): form = ApiKeyForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) if 'user_id' in form.cleaned_data.keys( ) and not request.user.is_superuser: raise ProblemDetailException(request, _("Insufficient permissions"), status=HTTPStatus.FORBIDDEN) api_key = ApiKey(user=request.user) form.populate(api_key) api_key.save() return SingleResponse(request, api_key, serializer=ApiKeySerializer.Base, status=HTTPStatus.CREATED)
def get(self, request, acquisition_id: uuid.UUID): try: acquisition = Acquisition.objects.get(pk=acquisition_id) except Acquisition.DoesNotExist: raise ProblemDetailException(request, _("Acquisition not found"), status=HTTPStatus.NOT_FOUND) if acquisition.relation != Acquisition.AcquisitionType.ACQUISITION.OPEN_ACCESS: self._authenticate(request) redis = Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DATABASE ) if request.user.is_anonymous: user_id = uuid.uuid4() else: user_id = request.user.pk redis.pfadd(f"popularity:{acquisition.entry_id}", str(user_id)) return FileResponse(acquisition.content, as_attachment=True, filename=acquisition.entry.title)
def put(self, request, catalog_id: UUID): form = CatalogForm.create_from_request(request) if not form.is_valid(): raise ValidationException(request, form) catalog = self._get_catalog(request, catalog_id) if Catalog.objects.exclude(pk=catalog.pk).filter( url_name=form.cleaned_data['url_name']).exists(): raise ProblemDetailException( request, title=_('Catalog url_name already taken'), status=HTTPStatus.CONFLICT) service = CatalogService() catalog = service.populate(catalog=catalog, form=form) return SingleResponse(request, catalog, serializer=CatalogSerializer.Detailed)
def post(self, request, catalog_id: uuid.UUID, entry_id: uuid.UUID): entry = self._get_entry(request, catalog_id, entry_id) try: metadata = json.loads(request.POST.get('metadata', '{}')) except json.JSONDecodeError as e: raise ProblemDetailException( request, title=_('Unable to parse metadata for request file'), status=HTTPStatus.BAD_REQUEST, previous=e ) form = AcquisitionMetaForm(metadata, request) if not form.is_valid(): raise ValidationException(request, form) acquisition = Acquisition( entry=entry, relation=form.cleaned_data.get('relation', Acquisition.AcquisitionType.ACQUISITION), mime=request.FILES['content'].content_type ) if 'content' in request.FILES.keys(): acquisition.content.save( f"{uuid.uuid4()}{mimetypes.guess_extension(acquisition.mime)}", request.FILES['content'] ) for price in form.cleaned_data.get('prices', []): Price.objects.create( acquisition=acquisition, currency=price['currency_code'], value=price['value'] ) return SingleResponse( request, acquisition, serializer=AcquisitionSerializer.Detailed, status=HTTPStatus.CREATED )
def put(self, request, author_id: UUID): form = CreateAuthorForm.create_from_request(request) author = self._get_author(request, author_id) if not form.is_valid(): raise ValidationException(request, form) if Author.objects.exclude(pk=author_id).filter( catalog=form.cleaned_data['catalog_id'], name=form.cleaned_data['name'], surname=form.cleaned_data['surname'], ).exists(): raise ProblemDetailException( request, _("Author already exists in the catalog"), status=HTTPStatus.CONFLICT) form.populate(author) author.save() return SingleResponse(request, author, serializer=AuthorSerializer.Detailed)