Giter Club home page Giter Club logo

djangorestframework-api-key's Introduction

Django REST Framework API Key

API key permissions for the Django REST Framework.

python versions django versions drf versions

Introduction

Django REST Framework API Key is a library for allowing server-side clients to safely use your API. These clients are typically third-party backends and services (i.e. machines) which do not have a user account but still need to interact with your API in a secure way.

Features

  • ✌️ Simple to use: create, view and revoke API keys via the admin site, or use built-in helpers to create API keys programmatically.
  • πŸ”’ As secure as possible: API keys are treated with the same level of care as user passwords. They are only visible at creation and hashed before storing in the database.
  • 🎨 Customizable: satisfy specific business requirements by building your own customized API key models, permission classes and admin panels.

Should I use API keys?

There are important security aspects you need to consider before switching to an API key access control scheme. We've listed some of these in Security caveats, including serving your API over HTTPS.

Besides, see Why and when to use API keys for hints on whether API keys can fit your use case.

API keys are ideal in the following situations:

  • Blocking anonymous traffic.
  • Implementing API key-based throttling. (Note that Django REST Framework already has may built-in utilities for this use case.)
  • Identifying usage patterns by logging request information along with the API key.

They can also present enough security for authorizing internal services, such as your API server and an internal frontend application.

Please note that this package is NOT meant for authentication. You should NOT use this package to identify individual users, either directly or indirectly.

If you need server-to-server authentication, you may want to consider OAuth instead. Libraries such as django-oauth-toolkit can help.

Quickstart

Install with pip:

pip install "djangorestframework-api-key==3.*"

Note: It is highly recommended to pin your dependency to the latest major version (as depicted above), as breaking changes may and will happen between major releases.

Add the app to your INSTALLED_APPS:

# settings.py

INSTALLED_APPS = [
  # ...
  "rest_framework",
  "rest_framework_api_key",
]

Run the included migrations:

python manage.py migrate

To learn how to configure permissions and manage API keys, head to the Documentation.

Changelog

See CHANGELOG.md.

Contributing

See CONTRIBUTING.md.

License

MIT

djangorestframework-api-key's People

Contributors

beegibson avatar brittandeyoung avatar chrismaddalena avatar davidfischer avatar dependabot[bot] avatar enprogames avatar fgrassals avatar florimondmanca avatar guilleijo avatar jabelone avatar jaswanthm avatar jeancochrane avatar jeffgodwyll avatar jimkring avatar mabdullahadeel avatar mariot avatar matveyvarg avatar spaceofmiah avatar syth0le avatar tony avatar utkucanbykl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

djangorestframework-api-key's Issues

Add instructions for reporting security vulnerabilities

Is your feature request related to a problem? Please describe.
GitHub automatically added the new docs/security.md file to issue templates, but it's currently lacking proper instructions on reporting vulnerabilities.

Describe the solution you'd like
Add instructions for reporting security vulnerabilities, i.e. who to contact, with what kind of information, and what the general process is.

Describe alternatives you've considered
/

Additional context
/

Investigate/add Django 3 support

Is your feature request related to a problem? Please describe.
Django 3 was in December 2019. We're still officially only supporting 2.0, 2.1 and 2.2

Describe the solution you'd like
Add support for Django 3. For this, we'd need to:

  • Add django==3.0 to the test suite.
  • Maybe consider not testing against 2.1 anymore, so as to keep the pipeline relatively slim?
  • Fix any breakage, e.g. in tests, or other APIs.
  • In case this requires us to introduce breaking changes, list them and let's discuss them to see if we'll need to issue a 2.0 release.

Additional context

Add support for Django 2.1

Description

We should add support for Django 2.1, although everything should normally be fine.

Build matrix in .travis.yml only includes Django up to 2.0, so it's only a matter of including it in the build matrix (⚠️ Python 3.5+).

Definition of done

  • The build tests for Django 2.1 on Python 3.5+
  • README.md is updated to display the additional support

Authentication with DRF_API_KEY_SECRET_KEY_HEADER only

Is your feature request related to a problem? Please describe.
It would be handy to allow client to use only a secret key value DRF_API_KEY_SECRET_KEY_HEADER to access endpoints.

Describe the solution you'd like
Client software provides only DRF_API_KEY_SECRET_KEY_HEADER and has its access to the endpoints and it is clear on the Django side to which client this key belongs to.

Describe alternatives you've considered
Adopt DRF token authentication for non-human clients.

Simplified autoincremented integer ID migration

Is your feature request related to a problem? Please describe.
Once #60 is solved, we should be able to provide a much simpler migration for the integer ID.

Describe the solution you'd like
Update AbstractAPIKey by dropping the custom id column, and generate a migration which simply drops the current id field and adds the autoincremented one.

As hinted in #42 (comment), adding/populating the new ID column and dropping the old ID column should be done in two separate migrations. This will allow users to take action in a state where both IDs exist.

We'll still need a migration guide for users with models referring to an APIKey model. See #48

Describe alternatives you've considered
/

Additional context
See #40 and #41 for the previous implementation

Hashed passwords aren't migrated across Django versions

When a hash is generated on one version of Django, it is not migrated when Django is upgraded.

According to the Django docs, salted passwords are saved thusly:

<algorithm>$<iterations>$<salt>$<hash>

As an aside, the Django docs go on to mention (emphasis added):

However, as computing power increases, the number of iterations needs to be increased. We’ve chosen a reasonable default (and will increase it with each release of Django), but you may wish to tune it up or down, depending on your security needs and available processing power.

What does this mean? It means on Django 2.0.x, with the following token/secret key, you get the following hash from using hash_token:

token: 8efdf11c76ff3f7cacfc671a449eafce
secret: QAR37TDpiEeP
hashed token: pbkdf2_sha256$100000$QAR37TDpiEeP$nJSAj17/1ejl1FP4UjLPHouiuTLCcNd3m0z85mwhW2g=

On Django 2.1.x, with the same secret key/token, the following hash is generated:

pbkdf2_sha256$120000$QAR37TDpiEeP$NILHvaJbgM6MmvFFVQvEhJQR49L7LEhq4cnpx0TAOGg=

You can see the <iterations> segment changes between versions, which changes the resulting <hash>.

I'm currently on v0.4.0, and I see that the hashing logic has been rewritten for the newer versions. Does using make_password avoid this kind of issue?

I'm willing to upgrade to v1.x and refactor my app, if this means DRFAK will handle migrating the hashes as Django increases the iterations it uses. Have you seen this issue come up in the past?

Django versions 2.0.x - 2.2.x
DRF versions 3.8.x - 3.9.x
DRF Api Key versions 0.3.x - 0.4.x

Add migration guide for integer PK field

Is your feature request related to a problem? Please describe.
#40 (Integer primary key field) got merged, but it includes a breaking change for users that have models that reference APIKey as a foreign key, e.g.:

class Person(models.Model):
    api_key = models.ForeignKey("APIKey")

A proper migration guide for users is a prerequisite for 2.0.

Describe the solution you'd like
Add a migration guide to a new docs/ folder. It should explain:

  • What the change is
  • How to check if one is affected
  • Steps to securely upgrade without losing existing data

This guide will be referenced in the changelog when preparing the 2.0 release.

Many hints were already provided by @eagle-r in #41, especially here: #41 (comment)

Another detailed plan for migrating FKs was shared here: #42 (comment)

Describe alternatives you've considered
/

Additional context
Please read #41 for the initial full conversation.

Python 3.8 support

We're a bit late on the 3.8 compatibility timeline, so let's push an officially compatible release.

There shouldn't be any incompabilities, so let's go the standard way:

  • Add 3.8 testing to the build.
  • Push a PR.
  • Fix any compatibility issues.
  • Release.

Seeking 100% test coverage

We're currently at ~96% test coverage. It might be worth investigating what parts of the codebase those remaining 4% correspond to, and try adding/tweaking our tests in order to get us to 100%. Then we can start enforcing full coverage on CI.

Any PR that gets us a little bit closer to that goal very much welcome!

Auto-create API Key on users created

Hello,
Thanks for this awesome package !
I would like to know if it's possible to auto-create the API key when a new user create an account ?

Complete switch to flake8

#79 replaced Pylint with Flake8 as our code style checker, but there are artifacts of Pylint across the code base. Let's clean up any remaining # pylint comments and other artifacts.

HasApiKeyAndIsAuthenticated permission.

Is your feature request related to a problem? Please describe.
Hey, i am using this library for a python backend with a react-native mobile client and them some API rest users. So one of my permissions setups requires the user to both use an Api key to authenticate the client (be that an app or third-party server using the API) and them some JWT to authenticate the specific user making the request, for example:

listing an history of items from the DB should only be possible when the requests comes from the mobile app and the user is logged. So that an user cant not access the API from outside the application unless i give them an APIKey

Describe the solution you'd like
Implement an HasApiKeyAndIsAuthenticated class on permissions.py, as i did here:

`
class HasAPIKeyAndIsAuthenticated(permissions.BasePermission):
 """Authorize if a valid API key is provided and the request is authenticated."""
    def has_permission(self, request, view):
        if(HasAPIKey().has_permission(request, view) and 
permissions.IsAuthenticated().has_permission(request, view)): 
            return True
        else: 
            return False

`

Describe alternatives you've considered
Is there a reason why this is not implemented already? would it be considered a bad practice?

Customization of permissions

Is your feature request related to a problem? Please describe.
As discussed in #34 (comment), the current HasAPIKey permission class does not allow to override the default authorization behavior, because of a hard dependency on the APIKey model.

Describe the solution you'd like

  • Introduce a BaseHasAPIKey mixin with .queryset and .get_queryset(), the latter defaulting to using .queryset (consistent with DRF code style).
  • Document that the queryset must expose .create_key().
  • Refactor HasAPIKey to default to queryset = APIKey.objects.all().

Describe alternatives you've considered
/

Additional context
permissions.py

UserAPIKey set user if authorised

Is your feature request related to a problem? Please describe.
In my system, every user should be able to create api keys. Once the user uses this key, I would like to have the user object in the request as he would login via JWT, or token based.

How could that work? I'm happy to implement it but I don't know what would be the best way. Any hits?

Cheers! And thanks for this awesome library

Internationalization

Is your feature request related to a problem? Please describe.
This package currently hardcodes strings such as verbose names and help texts, which means we can't generate translations for it.

Describe the solution you'd like

from django.utils.translation import gettext_lazy as _

and use _("...") everywhere. πŸ‘

Describe alternatives you've considered
/

Additional context
/

Identifying owner of APIKey

This looks like a great lib, though I have a few questions.

It appears as though this lib is positioned as an authorization lib, not authentication. As such, I assume that the authentication should happen before ApiKey creation? And because of that, I can assume it's authenticated when used to make a call to the API?

What's the best way to identify the owner of the ApiKey so I can manage which resources a particular request has access to? Is it as simple as using a FK to link this ApiKey to a user/organization/etc and then use that for filtering the querysets of the resources?

Change the PK field

Is your feature request related to a problem? Please describe.
The PK on the APIKey model is the string containing the prefix and hash. This makes it challenging to work with when referencing it in URLs in many REST-based frameworks. eg. 2TothlA4.pbkdf2_sha256$36000$dfPh6LIy7NB3$FTA61Pvuq3gres5ZsclXimNCRz70jvDRl/ygYOjEiLc=. This contains a slash and other special chars that would typically be URL encoded.

Describe the solution you'd like
Either convert the model to use a standard integer PK or provide some other means to reference it in such a way

Describe alternatives you've considered
I'm not sure, but could this be handled through the solution being considered for #34?

Proper prefix and hashed_key fields

Is your feature request related to a problem? Please describe.
As of 1.3, the ID of an API key is a concatenation of the prefix and hashed_key. These are available as properties on the model. We are willing to move to an integer ID in the future (see #41), but storing the prefix and hashed_key in their own fields is a necessary first step.

Describe the solution you'd like

  • Add prefix and hashed_key fields to AbstractAPIKey (they must be non-editable, and the prefix field must be unique).
  • Keep the ID as it is today, i.e. a concatenation of prefix and hashed_key.
  • Add a migration guide, addressing in particular users who may already be using AbstractAPIKey.

Describe alternatives you've considered
Keep things as they are, i.e. do the migration of prefix, hashed_key and idall in 2.0: this introduces much bigger changes and upgrade barriers. Populating the prefix and hashed_key introduces no breaking changes at all.

Additional context
/

Throttling helpers

Is your feature request related to a problem? Please describe.
Even though API keys can help block anonymous traffic, malicious actors (or even buggy third-party apps) may be able to cause damage by issuing too many requests. The DRF has built-in throttlers, but they rely on the IP or X-Forwarded-Proxy header. Throttling based on API keys would allow to limit usage, which may be used in the context of pricing plans.

Describe the solution you'd like
Provide a custom throttling helper based on API keys. It should use the API key prefix as the throttling key (instead of the IP or REMOTE_ADDR). We may be able to build upon DRF's builtin throttlers for the actual throttling logic.

Describe alternatives you've considered

  • Let users implement this themselves: this may need to be the case for customized API keys, but it'd be nice to provide some basic functionality.

Additional context
Mentioned/recommended here:

The use of PASSWORD_HASHERS should be documented more explicitly

When trying to set up I could not get authorization working for quite a while. Then I noticed that this library uses PASSWORD_HASHERS for hashing. For a general case it would work, but people running custom setups like me, may run into issues. In my case I had BCryptPasswordHasher as the top choice and the authorization fails with it. PBKDF2PasswordHasher works fine. Haven't really tested the others. So may I suggest a small section in README about this, along with a list of supported hashers.

Customization of admin panel

Is your feature request related to a problem? Please describe.
As discussed in #34 (comment), once we get base models/permissions/etc, we'll want to allow users to customize their API key admin panel as well.

Describe the solution you'd like
Document how to customize the admin (most likely via a Wiki page).

Describe alternatives you've considered

  • Create a BaseAPIKeyModelAdmin: at first sight, there may not be a need for this, but this should be confirmed first.

Additional context
admin.py

Type checking the test suite

As of #88, test_project/ and tests/ are not inspected by mypy. We'd need to perform various changes to make the test suite mypy-compliant, but it would help seeing how the type hints shipped with this package behave in a user setting.

Roadmap

Following up on discussions in #41, I'd like to expose to everyone ideas for the future of djangorestframework-api-key.

Project mission and summary

djangorestframework-api-key adds API key permissions to the Django REST Framework.

The current design allows to manage API keys within a given project. In the future, we want to provide better support for:

  • Customized API keys (e.g. linking API keys to another model via a foreign key).
  • Building API key management features (e.g. an API key management REST API serving a custom-made admin frontend, which involves views and serializers).

Timeline

Note: this timeline is updated as new versions are released. Only roadmap items for future releases are listed.

Current minor version: 1.4

v1.x:

  • #29 Scopes - Post-poned

v2.0: exposing API keys in views.

  • #61 (originally #40, a version of this is already in dev/2.0) Change PK to integer field.
  • #48 Migration guide for integer PK field

Future of 1.x

Upgrading to 2.0 may require a significant amount of effort for users that have models linked to APIKey. The risk of creating an "upgrade wall" and separating users between 1.x and 2.0 is real IMO.

But I'm not sure whether supporting both versions, and backporting new 2.x features into 1.x (at least for a certain amount of time) is worth it. It induces extra maintenance work, and with a clear migration guide most users should be able to upgrade without too much pain, and without losing any data.

So the plan is to discontinue 1.x support once 2.0 is out.

All of this is very much open to discussion. Feel free to share your thoughts/questions/insights! πŸ‘

Missing migration in Django 2.1.1

Description

While trying to integrate the library into another project (using Django 2.1.1), I generated migrations for one of that project's apps and it generated migrations for rest_framework_api_key:

Migrations for 'rest_framework_api_key':
  /Users/Florimond/.local/share/virtualenvs/personal-api-rzUhg_r6/lib/python3.7/site-packages/rest_framework_api_key/migrations/0002_auto_20180922_1508.py
    - Alter field revoked on apikey

We should generate them and release the updated version.

Definition of done

  • Migration mentioned above has been generated

API Reference

Is your feature request related to a problem? Please describe.
The docs currently does not have a reference of fields and class attributes available on APIKey, AbstractAPIKey and HasAPIKey. When users want to know how to use these, they'll typically need to look at the source code.

Describe the solution you'd like
Add an API Reference section to the docs site with this kind of information.

Describe alternatives you've considered
/

Additional context
/

Some links to source code are broken

Describe the bug
There are various links in the documentation that are broken, i.e. clicking on them returns a 404 page on GitHub. For example (there may be others):

This is because they use the old, flat rest_framework_api_key/... structure, whereas we recently switched to src/rest_framework_api_key/....

Possible solutions

Update the docs with up-to-date links.

Retrieving the raw API key from within a view

Is your feature request related to a problem? Please describe.
Currently, it's not obvious how we can access the raw API key from within a view that's protected behind a permission classes. (This may be useful in order to e.g. fetch resources associated to that key).

Users can access the request headers directly:

scheme, _, key = request.META["HTTP_AUTHORIZATION"].partition(" ")

but this essentially duplicates logic that's already defined on the HasAPIKey permission class applied to the view.

The only way to reuse that logic is to instantiate the permission class and call into .get_key()… But it doesn't feel very natural, does it?

class ProjectListView(APIView):
    permission_classes = [HasProjectAPIKey]

    def get(self, request: HttpRequest):
        key: str = HasProjectAPIKey().get_key(request)
        # ...

Describe the solution you'd like

Make .get_key() a class method, so that we can reuse it within a view a bit more naturally:

class ProjectListView(APIView):
    permission_classes = [HasProjectAPIKey]

    def get(self, request: HttpRequest):
        key: str = HasProjectAPIKey.get_key(request)
        # ...

Is this a breaking change? Luckily, it doesn't seem to be:

  • A class method is okay to use on an instance, so if people were already using the first way (HasProjectAPIKey().get_key()), they'll still be able to do it if we switch to a class method.
  • If people customized .get_key() (see Customization: Key parsing) and they defined it as an instance method, then surely they know how they're going to use it, and changing it on the base won't break anything for them anyway (even if they call super().get_key(), since that would also work within an instance method).

Describe alternatives you've considered

There are a couple of other ways we could have gone here, none of which are really okay:

  • Have BaseAPIKey set a magic .current_api_key (or similar) attribute on the view instance/class. We could make it work with type-checking, but it would lead to all sorts of weird edge cases and possible bugs related to shared view state. Also it probably wouldn't work with function-based views.
  • Introduce an APIKeyMiddleware that basically calls .get_key() and sets it on the request instance. This isn't great, because a) it would break type-checking (we're adding a new dynamic attribute on the request that isn't known to django-stubs), and b) it would expose the plaintext API key anywhere the request is accessible, which is a massive potential security hole if the developer isn't careful.
  • Introduce an APIKeyViewMixin that injects a .get_api_key(request) method on the view. That method would inspect the view's permission_classes. The problem is: there might be multiple API key permission classes set on the view, so which one would we use? "The one for which the API key is valid" won't work, because all permissions need to pass for a request to be authorized… So, nope.

The only sensible thing is to let the developer explicitly retrieve the raw API key from the request, using exact the permission class they want to use.

Additional context
Prompted by #93 (comment)

Seeking clarity on how to effectively using API keys

Hi !

I have a model structure like

User

  • Project 1
    -Res 1
    -Res 2
  • Project 2
    -Res 1
    -Res 2

Now the Top and all layer is projected by JWT auth for particular user

Now I want to user API key at Project level and below to configure it's resources via API key

So I created custom API key model like

class ProjectAPIKeyManager(BaseAPIKeyManager):
    def get_usable_keys(self):
        return super().get_usable_keys().filter(project__active=True)
class ProjectAPIKey(AbstractAPIKey):
    objects = ProjectAPIKeyManager()
    project = models.ForeignKey(
        Project,
        on_delete=models.CASCADE,
        related_name="api_keys",
    )

    class Meta(AbstractAPIKey.Meta):
        verbose_name = "Project API key"
        verbose_name_plural = "Project API keys"

Now I am not sure how to get Project from API key and then perform subsequent operations via that
Please help with the documentation or example project which can let me do that.

thanks in advance it looks a good framework I am just not sure how to use it for my requirements .

HTTP_AUTHORIZATION Stripped Out in AWS ElasticBeanstalk Deployments

I'm running an ElasticBeanstalk environment and spent hours trying to figure out why my API keys were not working in my deployed environments. Turns out that HTTP_AUTHORIZATION headers were being stripped out. Following a solution posted on the AWS forms, the header passes when you include a file with these contents in .ebextensions:

files:
  "/etc/httpd/conf.d/wsgihacks.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      WSGIPassAuthorization On

So you have to do some WSGI configuring to get it working. Not a bug, just thought that this would be helpful information to have here, or possibly in the documentation.

Customization of key generation

Is your feature request related to a problem? Please describe.
As discussed in #42 (comment), currently the key generation algorithm cannot be customized because of a hard dependency on generate_key() in APIKeyManager.

Note: key validation is already customizable via APIKeyManager.is_valid() and APIKey.is_valid().

Describe the solution you'd like

  • Introduce a BaseAPIKeyManager with .key_generator and .generate_key(), the latter defaulting to calling .key_generator.
  • Refactor APIKeyManager to be a subclass of BaseAPIKeyManager with key_generator = generate_key .

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context

Django 3 support

Django 3.0 is out, so we can start looking at adding support for it in this package.

Any contributions to make that happen gladly welcome!

Steps I think would make sense:

  1. Add django==3.0 to the test matrix.
  2. Run tests locally, or push as a PR to see if/how CI fails.
  3. Fix any minor issues.
  4. Open an issue in case of a major compatibility issue (though there shouldn't be any).

How to retrieve an APIKey object based on a key?

What's the recommended way of retrieving an APIKey instance based on a user-provided key? My use case is that I'd like to track the number of requests sent by each API key in my application.

The BaseAPIKeyManager does a similar lookup in its is_valid() method, where it seems like it's querying for the APIKey based on its prefix and then using the object instance's is_valid() method to double-check the key against the hashed version:

def is_valid(self, key: str) -> bool:
prefix, _, _ = key.partition(".")
queryset = self.get_usable_keys()
try:
api_key = queryset.get(prefix=prefix)
except self.model.DoesNotExist:
return False
if not api_key.is_valid(key):
return False
if api_key.has_expired:
return False
return True

Is it safe to perform a similar operation in my application (i.e. lookup the APIKey based on the prefix of the key provided by the user, and then double-check it using the APIKey.is_valid() method)?

Related to #20.

using name as service identifier

i am using 1.4.0
i have multiple services and i am restricting calls to some of them using api-key
let's say i have service A that will be consumed by many other services let's say just B and C
i am generating api-key for each B and C
the service A is doing some online automated payment tasks

is it safe to use the api-key name to identify which request come from which service (as long as i am generating unique names) or i do have to do something like linking the API keys to an organisation

calls to service A are restricted by firewall only services B and C can access it
is it safe to remove this constraint as long as the generated key is kept secret ?

1.4 release date

@florimondmanca: Can you please let us know what is the plan for V1.4 release? The feature of storing prefix and hashed_key separately in the AbstractAPIKey model is something that we are looking forward to.

Thanks again for this really nice package. :) (Please feel free to close this issue)

Add an Abstract Model

Is your feature request related to a problem? Please describe.
For a multi-tenant database, the original model may need to be extended to include some type of foreign key relationship.

Describe the solution you'd like
It would be nice to have a class like AbstractAPIKey where we can make our own customizations to the default model.

Describe alternatives you've considered
Having a mixin that can be used, along with some documentation for subclassing helpers, admin, and permissions.

Additional context
N/A

The document doesn't fully cover the custom model scenario.

Hi, Today I used your project into my project. I tried to apply the custom model. After migration, my custom model has been created on DB. I created a new row from admin and I got the key. However, this key didn't work. Because validation still looked APIKey due to permission_classes = HasApiKey configuration.

So I added my own HasAPIKey class with my custom model and the problem was solved. I thought that this situation can be added to the document.

from rest_framework_api_key.permissions import BaseHasAPIKey
class ClientHasAPIKey(BaseHasAPIKey):
    model = ClientAPIKey

permission_classes = ClientHasAPIKey

Admin Model Not Integrating into Django Admin Page

Went to add this app to my project and noticed it wasn't automatically added to my Django admin page. I tried manually adding it in my admin.py and it appeared:

from rest_framework_api_key.admin import APIKeyAdmin
from rest_framework_api_key.models import APIKey

admin.site.register(APIKey, APIKeyAdmin)

However, when I navigated to Home/API Key Permissions/API Keys I received the following error:

AttributeError at /admin/rest_framework_api_key/apikey/ Unable to lookup 'has_expired_func' on APIKey or APIKeyAdmin

My temporary fix was to create a new admin model by subclassing the one given by this app:

from rest_framework_api_key.admin import APIKeyAdmin
from rest_framework_api_key.models import APIKey

class BaseAPIKeyAdmin(APIKeyAdmin):

    list_display = (
        "name",
        "prefix",
        "created",
        "expiry_date",
        "revoked",
    )

admin.site.register(APIKey, BaseAPIKeyAdmin)

Thereby removing has_expired_func from list_display.

Consider dropping 3.5 support

According to PyPI Stats, the current distribution of users across Python minors is:

  • 3.7 (~65%)
  • 3.6 (~25%)
  • 3.8 (~5%)
  • 3.5 (<1%)

Virtually no one is using this package on 3.5, so we should consider dropping support. This will allow us to use variable type hints, and streamline our toolchain (e.g. Black only supports 3.6+). In general supporting 3 minors at a time seems like a good plan β€” so currently we should be on 3.6, 3.7 and 3.8.

Scopes (finer-grained permissions)

Is your feature request related to a problem? Please describe.
API keys are useful to identify a client, but the permission system could be more fine-grained by introducing a notion of "scope", i.e. what is the API key allowed to do?

Describe the solution you'd like

Scopes can be assigned via the admin (using a "Scopes" section in the APIKeyModelAdmin), or programmatically:

read_projects = Scope.objects.get_from_label("myapp.project.read")
api_key.scopes.add(read_projects)

create_projects = Scope.objects.get_from_label("myapp.project.create")
api_key.scopes.remove(create_projects)

Assigning a scope to an API key has the effect of granting it access to the views which require this scope, e.g.

from rest_framework.views import APIView
from rest_framework_api_key.permissions import HasAPIKeyWithScopes

class ProjectListView(APIView):
    permission_classes = [HasAPIKeyWithScopes]
    required_scopes = ["myapp.project.read"]

Like Django's permissions system, the x.y.read, x.y.create, x.y.update and x.y.delete scopes should be created automatically for each model y in each app x.

Custom scopes can be registered via settings:

API_KEY_CUSTOM_SCOPES = {
    "myapp": {
        "project": [
            ("publish", "Can publish a project")
        ]
    }
}

Implementation ideas

Models:

  • Scope: code*, verbose_name, description
  • ScopesMixin: scopes (M2M to Scope) + various helpers (.has_scope(), .has_scopes())
  • APIKey: add ScopesMixin to the class hierarchy.

Permissions:

  • Create APIKeyHasScopes permission class which inspects the required_scopes attribute on the view. Scopes in required_scopes are formatted as code:action.

Describe alternatives you've considered

  • Do not include builtin scopes: it would be cumbersome to have to create them manually, and the cost of creating/storing them is very low.
  • Custom scope discovery via a model's Meta.api_key_scopes: while it possible to allow extra attributes on Meta (by extending models.options.DEFAULT_NAMES), those extra attributes are not available during migrations (as models are faked at that time), which means custom scopes are not detected as they should be. On the other hand, settings are always available.

Additional context
@LeOntalEs implemented a flavor of this in his fork: tarasira#1

Useful inspiration:

Use a namespace for package-specific settings

Is your feature request related to a problem? Please describe.

The settings defined by djangorestframework-api-key do not use a proper namespace, which does not make them clearly identifiable and may induce clashes with settings from other packages.

Describe the solution you'd like

We should use a namespace for all package-specific settings, e.g. DRF_API_KEY_*.

Before we only support namespaced settings, existing settings should be deprecated in next version (v0.3):

  • Add support for namespaced settings, i.e. DRF_API_KEY_TOKEN_HEADER and DRF_API_KEY_SECRET_KEY_HEADER.
  • Drop support for non-namespaced settings.
  • Add system checks for non-namespaced settings indicating that they should be upgraded.

Describe alternatives you've considered

We could also have used a REST_FRAMEWORK_API_KEY settings dictionary (like DRF does), but it seems overkill/non-necessary for now.

Additional context

Official support for configurable hashing algorithm will be added in the future, and the existing, unofficial SECRET_KEY_ALGORITHM was inconsistent with the headers settings, which highlighted the need for a settings namespace.

Optional expiry date

Is your feature request related to a problem? Please describe.
At the moment, an API key is forever valid. It may be useful to create temporary and/or renewable API keys, with a limited lifespan and hence an expiry date.

Describe the solution you'd like

  • Allow to define an API key's expiry date upon creation, or set one after creation.
  • If no expiry date is set, the API key never expires (current behavior).
  • If one is set (e.g. 2019-05-11), the API key should expire at midnight UTC (i.e. 2019-05-12 00:00 UTC).

Describe alternatives you've considered

  • Require an expiry date: this would both break current behavior and be too much for simplest use cases.

Implementation ideas

  • Add a nullable and blank .expiry_date field to APIKey, and create a migration.
  • Update HasAPIKey to check the expiry date on the API key, if applicable.

Additional context
This was originally mentioned by @ssrebelious in #16 (comment).

Permissions do not implement has_object_permission

Hello. Really like your package. Here's a problem I've ran into while implementing an api based on it.
I have a user REST API and I wanted to allow updating user only for the user himself or an apikey. I wrote a simple permission called IsTheSameUser, but chaining it using OR with HasAPIKey didn't work - requests were passing.
Looking into it, I noticed that permissions provided by this project don't implement has_object_permission and my permission class cannot implement has_permission, since it's only becomes aware at has_object_permission level.
So, may I suggest adding has_object_permission method that wraps has_permisison:

    def has_object_permission(self, request, view, obj):
        return self.has_permission(request, view)

I subclassed for now, don't see any harm that can be done with this addition.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.