Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'master' into add-django-axes
  • Loading branch information
rolandgeider committed Oct 13, 2022
2 parents 6568666 + f38a27c commit 5e3167e
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 31 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/docker-base.yml
Expand Up @@ -17,10 +17,10 @@ jobs:
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.1.0

- name: Login to DockerHub
uses: docker/login-action@v2.0.0
Expand All @@ -29,7 +29,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build base image
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: .
push: true
Expand Down
38 changes: 29 additions & 9 deletions .github/workflows/docker.yml
Expand Up @@ -6,38 +6,58 @@ on:
- master

jobs:
path-context:
apache:
name: Build apache image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.1.0

- name: Login to DockerHub
uses: docker/login-action@v2.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build apache image
uses: docker/build-push-action@v3.1.1
- name: Build image
uses: docker/build-push-action@v3.2.0
with:
context: .
push: true
file: extras/docker/demo/Dockerfile
platforms: linux/amd64,linux/arm64
tags: wger/demo:latest,wger/demo:2.1-dev,wger/apache:latest,wger/apache:2.1-dev
tags: wger/demo:latest,wger/demo:2.2-dev,wger/apache:latest,wger/apache:2.2-dev

- name: Build dev image
uses: docker/build-push-action@v3.1.1
prod:
name: Build production image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.1.0

- name: Login to DockerHub
uses: docker/login-action@v2.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build image
uses: docker/build-push-action@v3.2.0
with:
context: .
push: true
file: extras/docker/development/Dockerfile
platforms: linux/amd64,linux/arm64
tags: wger/server:latest,wger/server:2.1-dev,wger/devel:latest,wger/devel:2.1-dev
tags: wger/server:latest,wger/server:2.2-dev,wger/devel:latest,wger/devel:2.2-dev
9 changes: 8 additions & 1 deletion extras/docker/development/settings.py
Expand Up @@ -44,7 +44,7 @@
TIME_ZONE = env.str("TIME_ZONE", 'Europe/Berlin')

# Make this unique, and don't share it with anybody.
SECRET_KEY = env.str("SECRET_KEY", 'wger-django-secret-key')
SECRET_KEY = env.str("SECRET_KEY", 'wger-docker-supersecret-key-1234567890!@#$%^&*(-_)')


# Your reCaptcha keys
Expand Down Expand Up @@ -122,3 +122,10 @@
AXES_FAILURE_LIMIT = 5 # configurable, default is 5
AXES_COOLOFF_TIME = 0.5 # configurable, default is 0.5 hours
AXES_HANDLER = 'axes.handlers.cache.AxesCacheHandler' # Configurable, but default is the cache handler

#
# Django Rest Framework SimpleJWT
#
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(minutes=env.int("ACCESS_TOKEN_LIFETIME", 15))
SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] = timedelta(hours=env.int("REFRESH_TOKEN_LIFETIME", 24))
SIMPLE_JWT['SIGNING_KEY'] = env.str("SIGNING_KEY", SECRET_KEY)
5 changes: 3 additions & 2 deletions requirements.txt
Expand Up @@ -12,13 +12,13 @@ django-activity-stream~=1.4
django-axes==5.39.0
django-crispy-forms~=1.14
django-simple-history~=3.1
django-email-verification~=0.1.0
django-email-verification~=0.3.1
django_compressor~=4.1
django_extensions~=3.2
django-storages~=1.13
django-environ==0.9.0
easy-thumbnails==2.8.3
fontawesomefree~=6.1.1
fontawesomefree~=6.2.0
icalendar==4.1.0
invoke==1.7.3
pillow==9.2.0
Expand All @@ -33,6 +33,7 @@ requests==2.28.1
django-cors-headers==3.13.0
django-filter==22.1
djangorestframework~=3.14
djangorestframework-simplejwt[crypto]==5.2.1

# Not used anymore, but needed because some modules are imported in DB migration
# files
Expand Down
22 changes: 20 additions & 2 deletions wger/core/api/views.py
Expand Up @@ -198,14 +198,23 @@ def get(request):
class UserAPILoginView(viewsets.ViewSet):
"""
API endpoint for api user objects
.. warning:: This endpoint is deprecated
"""
permission_classes = (AllowAny, )
queryset = User.objects.all()
serializer_class = UserApiSerializer
throttle_scope = 'login'

def get(self, request):
return Response({'message': "You must send a 'username' and 'password' via POST"})
return Response(
data={
'message': "You must send a 'username' and 'password' via POST",
'warning': "This endpoint is deprecated."
},
headers={
"Deprecation": "Sat, 01 Oct 2022 23:59:59 GMT",
},
)

def post(self, request):
data = request.data
Expand All @@ -223,7 +232,16 @@ def post(self, request):
)

token = create_token(form.get_user())
return Response({'token': token.key}, status=status.HTTP_200_OK)
return Response(
data={
'token': token.key,
'message': "This endpoint is deprecated."
},
status=status.HTTP_200_OK,
headers={
"Deprecation": "Sat, 01 Oct 2022 23:59:59 GMT",
}
)


class UserAPIRegistrationViewSet(viewsets.ViewSet):
Expand Down
13 changes: 6 additions & 7 deletions wger/exercises/models/exercise.py
Expand Up @@ -116,13 +116,12 @@ def get_absolute_url(self):
"""
Returns the canonical URL to view an exercise
"""
return reverse(
'exercise:exercise:view-base',
kwargs={
'pk': self.exercise_base_id,
'slug': slugify(self.name)
}
)
slug_name = slugify(self.name)
kwargs = {'pk': self.exercise_base_id}
if slug_name:
kwargs['slug'] = slug_name

return reverse('exercise:exercise:view-base', kwargs=kwargs)

def save(self, *args, **kwargs):
"""
Expand Down
38 changes: 38 additions & 0 deletions wger/exercises/tests/test_exercise_model.py
@@ -0,0 +1,38 @@
# This file is part of wger Workout Manager.
#
# wger Workout Manager is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# wger Workout Manager is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License

# wger
from wger.core.tests.base_testcase import WgerTestCase
from wger.exercises.models import Exercise


class ExerciseModelTestCase(WgerTestCase):
"""
Test the logic in the exercise model
"""

def test_absolute_url_name(self):
"""Test that the get_absolute_url returns the correct URL"""
exercise = Exercise(exercise_base_id=1, description='abc', name='foo')
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/1/view-base/foo')

def test_absolute_url_no_name(self):
"""Test that the get_absolute_url returns the correct URL"""
exercise = Exercise(exercise_base_id=2, description='abc', name='')
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/2/view-base')

def test_absolute_url_no_name2(self):
"""Test that the get_absolute_url returns the correct URL"""
exercise = Exercise(exercise_base_id=42, description='abc', name='@@@@@')
self.assertEqual(exercise.get_absolute_url(), '/en/exercise/42/view-base')
18 changes: 16 additions & 2 deletions wger/settings_global.py
Expand Up @@ -18,6 +18,7 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import re
from datetime import timedelta


"""
Expand Down Expand Up @@ -82,6 +83,7 @@
'rest_framework',
'rest_framework.authtoken',
'django_filters',
'rest_framework_simplejwt',

# Breadcrumbs
'django_bootstrap_breadcrumbs',
Expand Down Expand Up @@ -418,6 +420,7 @@
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
Expand All @@ -429,6 +432,17 @@
}
}

#
# Django Rest Framework SimpleJWT
#
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
}

#
# CORS headers: allow all hosts to access the API
#
Expand Down Expand Up @@ -489,8 +503,8 @@ def email_verified_callback(user):
EMAIL_MAIL_SUBJECT = 'Confirm your email'
EMAIL_MAIL_HTML = 'email_verification/email_body_html.tpl'
EMAIL_MAIL_PLAIN = 'email_verification/email_body_txt.tpl'
EMAIL_TOKEN_LIFE = 60 * 60
EMAIL_PAGE_TEMPLATE = 'email_verification/confirm_template.html'
EMAIL_MAIL_TOKEN_LIFE = 60 * 60
EMAIL_MAIL_PAGE_TEMPLATE = 'email_verification/confirm_template.html'
EMAIL_PAGE_DOMAIN = 'http://localhost:8000/'

#
Expand Down
42 changes: 39 additions & 3 deletions wger/software/templates/api.html
Expand Up @@ -16,11 +16,47 @@ <h3>Authentication</h3>
objects such as workouts, you need to generate an API KEY</strong> and pass
it in the header, see the link on the sidebar for details.</p>

<p>You can also generate a token via the <code>login</code> endpoint. Send a
username and password and you will get the user's token or a new one will be
generated. At the moment it is not possible to register via the API.</p>
<h6>JWT Authentication</h6>

<p>
You can generate access token via <code>/token/</code> endpoint. Send a username and password, and you will get the
<code>access</code> token which you can use to access the private endpoints.
<pre>
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"username": "example_username", "password": "example_password "}' \
https://wger.de/api/v2/token/

...
{
"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU",
"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"
}
</pre>

<p>Additionally, you can send an access token to <code>/token/verify/</code> endpoint to verify that token.</p>

<p>When this short-lived access token expires, you can use the longer-lived <code>refresh</code>
token to obtain another access token.
<pre>
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \
https://wger.de/api/v2/token/refresh/

...
{"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNTY3LCJqdGkiOiJjNzE4ZTVkNjgzZWQ0NTQyYTU0NWJkM2VmMGI0ZGQ0ZSJ9.ekxRxgb9OKmHkfy-zs1Ro_xs1eMLXiR17dIDBVxeT-w"}
</pre>
</p>
</p>

<p>You should always use HTTPS if possible when communicating with the server.</p>
<p>At the moment it is not possible to register via the API.</p>
<p><strong>Deprecated: </strong>You can also generate a token via the <code>login</code> endpoint. Send a
username and password, and you will get the user's token or a new one will be
generated.</p>


<div class="container">
Expand Down
12 changes: 10 additions & 2 deletions wger/urls.py
Expand Up @@ -30,6 +30,11 @@
# Third Party
from django_email_verification import urls as email_urls
from rest_framework import routers
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)

# wger
from wger.core.api import views as core_api_views
Expand All @@ -44,7 +49,7 @@
from wger.weight.api import views as weight_api_views


#admin.autodiscover()
# admin.autodiscover()

#
# REST API
Expand Down Expand Up @@ -204,7 +209,7 @@
# The actual URLs
#
urlpatterns = i18n_patterns(
#url(r'^admin/', admin.site.urls),
# url(r'^admin/', admin.site.urls),
path('', include(('wger.core.urls', 'core'), namespace='core')),
path('workout/', include(('wger.manager.urls', 'manager'), namespace='manager')),
path('exercise/', include(('wger.exercises.urls', 'exercise'), namespace='exercise')),
Expand Down Expand Up @@ -244,6 +249,9 @@
core_api_views.UserAPIRegistrationViewSet.as_view({'post': 'post'}),
name='api_register'
),
path('api/v2/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/v2/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/v2/token/verify/', TokenVerifyView.as_view(), name='token_verify'),

# Others
path(
Expand Down

0 comments on commit 5e3167e

Please sign in to comment.