Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

devel: rest api token auth #469

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion devel/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def clean_pgp_key(self):

class Meta:
model = UserProfile
exclude = ('allowed_repos', 'user', 'repos_auth_token')
exclude = ('allowed_repos', 'user', 'repos_auth_token', 'api_token')
widgets = {
'yob': NumberInput(attrs={'min': 1950, 'max': date.today().year - 10}),
}
Expand Down
44 changes: 44 additions & 0 deletions devel/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,55 @@
from django.contrib.auth.models import User, Group
from django_countries.fields import CountryField
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import MiddlewareMixin

from .fields import PGPKeyField
from main.utils import make_choice, set_created_field

from planet.models import Feed


class AuthTokenBackend(object):
def authenticate(self, request, username=None, password=None):
if 'X-Archweb-Token' in request.headers:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to use Authorization: Bearer as header, this is a quite widely used way for such API and would also allow a lot of HTTP clients to use implemented helper methods for authenticate a request with a token. Otherwise you'd need to pass around custom header strings like X-Archweb-Token.
What are your thoughts about this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds fine to me, it shouldn't conflict with things I suppose.

try:
profile = UserProfile.objects.get(api_token=request.headers['X-Archweb-Token'])
except UserProfile.DoesNotExist:
return None

print(profile)
return profile.user
return None

class AuthTokenMiddleware(MiddlewareMixin):
header = "X-Archweb-Token"

def process_request(self, request):
print("process", request)
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, "user"):
raise ImproperlyConfigured(
"The Django token user auth middleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the RemoteUserMiddleware class."
)
try:
token = request.headers[self.header]
print(token)
except KeyError:
return

try:
profile = UserProfile.objects.get(api_token=token)
except UserProfile.DoesNotExist:
return None

request.user = profile.user


class UserProfile(models.Model):
latin_name = models.CharField(
max_length=255, null=True, blank=True, help_text="Latin-form name; used only for non-Latin full names")
Expand Down Expand Up @@ -56,6 +98,7 @@ class UserProfile(models.Model):
rebuilderd_updates = models.BooleanField(
default=False, help_text='Receive reproducible build package updates')
repos_auth_token = models.CharField(max_length=32, null=True, blank=True)
api_token = models.CharField(max_length=32, null=True, blank=True)
last_modified = models.DateTimeField(editable=False)

class Meta:
Expand Down Expand Up @@ -198,6 +241,7 @@ def delete_user_model(sender, **kwargs):
return

userprofile.repos_auth_token = ''
userprofile.api_token = ''

Feed.objects.filter(website_rss=userprofile.website_rss).delete()

Expand Down
8 changes: 7 additions & 1 deletion devel/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ def tier0_mirror_auth(request):
def change_profile(request):
profile, _ = UserProfile.objects.get_or_create(user=request.user)
if request.POST:
# Generate token
if request.POST.get('api_token'):
profile.api_token = generate_repo_auth_token()
profile.save()

form = ProfileForm(request.POST)
profile_form = UserProfileForm(request.POST,
request.FILES,
Expand Down Expand Up @@ -257,7 +262,8 @@ def change_profile(request):
profile_form = UserProfileForm(instance=profile)
return render(request, 'devel/profile.html',
{'form': form,
'profile_form': profile_form})
'profile_form': profile_form,
'profile': profile})


@login_required
Expand Down
5 changes: 5 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@
# Set django's User stuff to use our profile model
AUTH_PROFILE_MODULE = 'devel.UserProfile'

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)

MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'devel.models.AuthTokenMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
Expand Down
15 changes: 15 additions & 0 deletions templates/devel/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,20 @@ <h2>Developer Profile</h2>
<p><label></label> <input title="Save changes" type="submit" value="Save" /></p>
</form>

<form id="api-profile-form" enctype="multipart/form-data" method="post" action="">{% csrf_token %}
<h3>API token</h3>
<p>Token for completing todolist items with for example, rebuild-todo</p>
{% if profile.api_token is None %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it would be a good pattern to be able to revoke a token, or to phrase it differently: Generate a new token and invalidate the existing one.
I'd probably propose something like only showing the token once when the generate button is clicked and otherwise show an invalidate/regenerate button instead. We should hide the token after generating it.

<fieldset>
<input type="hidden" name="api_token" id="api_token" value="generate">
</fieldset>
<p><label></label> <input title="Generate token" type="submit" value="Generate API Token" /></p>
{% else %}
<fieldset>
<label>Token:</label>
{{ profile.api_token }}
</fieldset>
{% endif %}
</form>
</div>
{% endblock %}