Skip to content
Open
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
23 changes: 23 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
qrcode = ">=5.1"
xhtml2pdf = ">=0.2.7"
django = "==4.2.4"
djangorestframework = "==3.14.0"
django-cors-headers = "*"
djangorestframework-jsonapi = "*"
pymysql = ">=0.8.0"
requests = ">=2.20.0"
authlib = "==0.6"
faker = "==0.8.15"
django-extensions = "==3.2.3"
joserfc = "==0.6.0"

[dev-packages]

[requires]
python_version = "3.11"
164 changes: 104 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,110 +1,154 @@
# Woolly - API

Woolly is the online shop of all the [associations of the Université de Technologie de Compiègne](https://assos.utc.fr).
Woolly is the online shop of all the clubs of the Université de Technologie de Compiègne: https://assos.utc.fr/woolly/.

## Prerequisites

## Installation

### Prerequisites

First of all, you need to set up the environment.
You will need [Python version 3](https://www.python.org/downloads/) and [pip (a Python packages manager)](https://pypi.org/project/pip/).
You will need to install:
- [pyenv](https://github.com/pyenv/pyenv): to manage Python versions
- [pipenv](https://github.com/pypa/pipenv): to manage dependencies

## Installation

Then we will set up the virtual environment with `virtualenv`.
```sh
pip install virtualenv
Simply run the following commands to install all dependencies for deployment and development:
```bash
make init
make init-dev
```

Create the `venv` folder :
On Linux :
```sh
virtualenv -p python3 "venv" # python3 is the name of python executable
```
On Windows :
```sh
virtualenv "venv"
```
Now copy `example.env` to `.env` and fill the secrets within.

Now activate the virtual environment. You have to do this everytime you want to work with Woolly and don't see the `(venv)` on the left of your terminal prompt.
```sh
# On Linux :
source venv/bin/activate
# On Windows :
venv\Scripts\activate
Make sure the database you are using is set to UTF-8 with:
```sql
ALTER DATABASE woolly CHARACTER SET utf8;
```

Finally you need to migrate models into the database:
```bash
make db-migrate
```

### Installation
## Development

With the virtual environment activated, install all the required librairies :
```sh
pip install -r requirements.txt
You can launch the development server on http://localhost:8000 with:
```bash
make run
```

You can find [the documentation of the API here](./documentation/api.md).

Now ask a responsible person for the `settings_confidential.py` file containing the foreign APIs identification keys. The file is to be placed next to the `settings.py` file. There is a placeholder file called `settings_confidential.example.py`, you can copy and fill it.
## Using Docker

If you are using [Docker](https://docker.com/) you can skip Installation and Development, but you will still need to copy and fill your `.env` file.

Create your database named `woolly`, set charset to UTF-8 with :
```sql
ALTER DATABASE `woolly` CHARACTER SET utf8;
Build the docker image with:
```bash
make build
```

Then you need to migrate, and initialize the database :
```sh
python manage.py migrate
python manage.py loaddata usertypes
Run it with:
```bash
docker run --rm -it --env-file .env -p 8000:8000 woolly-api:dev
```

You also need to generate all static files :
```sh
python manage.py collectstatic
You can run other commands with:
```bash
docker run --rm -it --env-file .env -p 8000:8000 woolly-api:dev <command>
```

Finally, you can launch the server.
```sh
python manage.py runserver
If you have trouble accessing your database from the container, replace `localhost` by `host.docker.internal` in `DATABASE_URL`.

## Deployment

For deployment it is easier to install the virtual environment in the same folder:
```bash
export PIPENV_VENV_IN_PROJECT="enabled"
pipenv install --deploy
```

You can now play with the server on http://localhost:8000
Generate all static files with:
```bash
pipenv run python manage.py collectstatic
```

You can find (the documentation of the API here)[./documentation/api.md].
Read Django instructions here: https://docs.djangoproject.com/en/3.1/howto/deployment/

In the following configuration, `/path_to_woolly` corresponds to the absolute file path to where you installed the Woolly API git repository and `/url_to_woolly` to the base url where you want to deploy the API.

## Deployment
### Deploy static files with Apache

Check this deployment checklist : https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
Add this to a Apache config file (like `/etc/apache2/apache2.conf`):
```ini
Alias /url_to_woolly/static /path_to_woolly/static
<Directory /path_to_woolly/static>
Require all granted
</Directory>
```

### Deploy app using mod_wsgi and Apache

Deploy server using : https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
Documentation: https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/modwsgi/

Here, on deployment with use Apache to run the server.
Install `mod_wsgi` : `sudo apt-get install libapache2-mod-wsgi-py3`
Restart apache for mod_wsgi to be effective : `sudo systemctl restart apache2`
First, install mod_wsgi and restart apache:
```bash
sudo apt-get install libapache2-mod-wsgi-py3
sudo systemctl restart apache2
```

In an Apache config file (like `/etc/apache2/apache2.conf`), replace `BASE_FOLDER` with the path to where you installed Woolly :
In the Apache config:
```ini
ServerName YOUR_SERVER_NAME
WSGIScriptAlias / BASE_FOLDER/woolly_api/wsgi.py
WSGIDaemonProcess woolly-api python-home=BASE_FOLDER/venv python-path=BASE_FOLDER
WSGIScriptAlias /url_to_woolly /path_to_woolly/woolly_api/wsgi.py
WSGIDaemonProcess woolly-api python-home=/path_to_woolly/.venv python-path=/path_to_woolly
WSGIProcessGroup woolly-api
WSGIPassAuthorization On

<Directory BASE_FOLDER>
<Directory /path_to_woolly>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
```
And restart Apache : `sudo systemctl restart apache2`.

You have to restart Apache each time you modify your application for the changes to be applied.
And reload Apache configuration:
```bash
sudo systemctl reload apache2
```

You have to reload Apache each time you modify your application for the changes to be applied.

### Deploy app using ProxyPass and Apache

If you are running Djanog behind a reverse proxy you might need to add the following lines to your `.env`:
```
RUN_THROUGH_PROXY=True
BASE_URL=/url_to_woolly
```

Then in the Apache config:
```ini
ProxyPreserveHost On
ProxyPassMatch ^/url_to_woolly/static !
ProxyPass /url_to_woolly http://localhost:8444
ProxyPassReverse /url_to_woolly http://localhost:8444
```

## Need help ?

Don't forget to check the `./documentation/` folder for more documentation on Woolly.
Here are some useful commands:
```bash
# Activate the pipenv environment
pipenv shell
# Run a interactive shell to interact with Woolly
python manage.py shell_plus
# Check the defined models
python manage.py inspectdb
# Check all TODOs, FIXME, ...
python manage.py notes
```

## License
Don't forget to check the [./documentation/](./documentation/) folder for more documentation on Woolly.

This project is licensed under the GNU General Public License v3.0 - see the [LICENSE.md](LICENSE.md) file for details
## License

This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](./LICENSE) file for details
15 changes: 8 additions & 7 deletions authentication/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import logging

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.utils.translation import gettext as _
from django.forms import ValidationError
from rest_framework import authentication
from rest_framework import exceptions
from .services import JWTClient

from woolly_api.settings import JWT_SECRET_KEY
from .helpers import get_jwt_from_request
from joserfc.jwt import decode as decode_jwt

UserModel = get_user_model()

Expand All @@ -22,10 +26,8 @@ def authenticate(self, request):
if jwt is None:
return None

# Get user id
jwtClient = JWTClient()
try:
claims = jwtClient.get_claims(jwt)
claims = decode_jwt(jwt, JWT_SECRET_KEY).claims
user_id = claims['data']['user_id']
except:
return None
Expand All @@ -34,18 +36,17 @@ def authenticate(self, request):

# Try logging user
try:
user = UserModel.objects.get(id=user_id)
return UserModel.objects.get(id=user_id) , None
except UserModel.DoesNotExist:
raise exceptions.AuthenticationFailed("User does not exist.")
return (user, None)


class AdminSiteBackend(ModelBackend):
"""
Authenticate User from email - password, used in the admin site
"""

def authenticate(self, request, username=None, password=None):
def authenticate(self, request, username=None, password=None, **kwargs):
# Try to fetch user
try:
user = UserModel.objects.get(**{ UserModel.USERNAME_FIELD: username })
Expand Down
5 changes: 3 additions & 2 deletions authentication/helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from authlib.specs.rfc7519 import JWTError
import logging

from .models import User, UserType
from .serializers import UserSerializer

Expand All @@ -12,7 +13,7 @@ def get_jwt_from_request(request):
if not jwt or jwt == '':
return None
if jwt[:7] != 'Bearer ':
raise JWTError("Authorization header doesn't begin with 'Bearer '")
raise Exception("Invalid JWT format. (Bearer missing)")
return jwt[7:] # substring : Bearer ...


Expand Down
2 changes: 1 addition & 1 deletion authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='user',
name='usertype',
field=models.ForeignKey(default=4, on_delete=None, related_name='users', to='authentication.UserType'),
field=models.ForeignKey(default=4, on_delete=models.DO_NOTHING, related_name='users', to='authentication.UserType'),
),
]
2 changes: 1 addition & 1 deletion authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class User(AbstractBaseUser):
birthdate = models.DateField(default=datetime.date.today)

# Relations
usertype = models.ForeignKey(UserType, on_delete=None, null=False, default=4, related_name='users')
usertype = models.ForeignKey(UserType, on_delete=models.DO_NOTHING ,null=False, default=4, related_name='users')
# associations = models.ManyToManyField('sales.Association', through='sales.AssociationMember')

# Rights
Expand Down
21 changes: 8 additions & 13 deletions authentication/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
from django.utils.crypto import get_random_string
from django.core.cache import cache

from authlib.specs.rfc7519 import JWT, JWTError
from authlib.client import OAuth2Session, OAuthException
import time

from woolly_api.settings import DEBUG, JWT_SECRET_KEY, JWT_TTL, OAUTH as OAuthConfig
from .helpers import get_jwt_from_request, find_or_create_user
from .models import User, UserType
from .serializers import UserSerializer
from joserfc.jwt import decode as decode_jwt, encode as encode_jwt


class JWTClient(JWT):
class JWTClient:
"""
Authentification des utilisateurs entre le front et Woolly API
Methods:
Expand Down Expand Up @@ -44,20 +41,18 @@ def get_claims(self, jwt):
Return the JSON content of a JWT
"""
try:
self.validate(jwt)
return self.claims
except JWTError as error:
return decode_jwt(jwt, JWT_SECRET_KEY)
except:
return {
'error': 'JWTError',
'message': str(error)
'error': 'InvalidJWT',
'message': 'JWT is invalid'
}

def validate(self, jwt):
"""
Stock claims if jwt is valid, else raise a JWTError
"""
self.claims = self.decode(jwt, JWT_SECRET_KEY)
self.claims.validate()
self.claims = decode_jwt(jwt, JWT_SECRET_KEY)

def create_jwt(self, user_id, session_key):
"""
Expand All @@ -77,7 +72,7 @@ def create_jwt(self, user_id, session_key):
'session': session_key,
}
}
token = self.encode(header, payload, JWT_SECRET_KEY).decode('utf-8')
token = encode_jwt(header, payload, JWT_SECRET_KEY)
return {
'token_type': 'Bearer',
'token': token,
Expand Down
Loading