Skip to content

Commit

Permalink
Merge branch 'release/0.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
avara1986 committed Mar 25, 2018
2 parents b85d04a + 92d9847 commit 2e587e3
Show file tree
Hide file tree
Showing 21 changed files with 546 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
include=
*project/*
omit =
venv/*
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,9 @@ ENV/

# mypy
.mypy_cache/

.idea

pylintReport.txt
db.sqlite3
_build
18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
language: python
sudo: false
cache: false
python:
- '3.6'
install:
- pip install -r requirements-tests.txt

script:
- coverage erase
- coverage run -m unittest
after_success:
- coverage combine
- coveralls

notifications:
email:
- a.vara.1986@gmail.com
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.6.4-alpine3.7

RUN apk add --update curl gcc g++ libffi-dev openssl-dev python3-dev \
&& rm -rf /var/cache/apk/*
RUN ln -s /usr/include/locale.h /usr/include/xlocale.h

ENV PYTHONUNBUFFERED=1 ENVIRONMENT=pre APP_HOME=/microservice/

RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD requirement*.txt $APP_HOME
RUN pip install -r requirements-docker.txt
ADD . $APP_HOME

EXPOSE 5000

CMD ["gunicorn", "--worker-class", "eventlet", "--workers", "8", "--log-level", "INFO", "--bind", "0.0.0.0:5000", "manage:app"]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# oauth
# oauth
OAuth with JWT Python Microservices

[![Build Status](https://travis-ci.org/python-microservices/oauth.svg?branch=master)](https://travis-ci.org/python-microservices/oauth)
[![Coverage Status](https://coveralls.io/repos/github/python-microservices/oauth/badge.svg?branch=master)](https://coveralls.io/github/python-microservices/oauth?branch=master)
[![Requirements Status](https://requires.io/github/python-microservices/oauth/requirements.svg?branch=master)](https://requires.io/github/python-microservices/oauth/requirements/?branch=master)
24 changes: 24 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# encoding: utf-8
from flask_script import Manager

from project import create_app

app, db = create_app()

manager = Manager(app)


@manager.command
def create_db():
"""Creates the db tables."""
db.create_all()


@manager.command
def drop_db():
"""Drops the db tables."""
db.drop_all()


if __name__ == '__main__':
manager.run()
96 changes: 96 additions & 0 deletions project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# encoding: utf-8

import os

from flasgger import Swagger
from flask import Flask

from project.config import CONFIG

__author__ = "Alberto Vara"
__email__ = "a.vara.1986@gmail.com"
__version__ = "0.0.1"

ENVIRONMENT = os.environ.get("ENVIRONMENT", "default")

SWAGGER_CONFIG = {
"headers": [
],
"specs": [
{
"endpoint": 'apispec_1',
"route": '{application_root}/apispec_1.json',
"rule_filter": lambda rule: True, # all in
"model_filter": lambda tag: True, # all in
}
],
"info": {
"title": "API ",
"description": "API para...",
"contact": {
"responsibleOrganization": "ME",
"responsibleDeveloper": "Me",
"email": "me@me.com",
},
"version": "0.0.1"
},
"securityDefinitions": {
"APIKeyHeader": {"type": "apiKey", "name": "Authorization", "in": "header"},
},
"static_url_path": "{application_root}/flasgger_static",
"swagger_ui": True,
"uiversion": 2,
"specs_route": "/apidocs/",
"basePath": "{application_root}"
}


class PrefixMiddleware(object):

def __init__(self, app, prefix=''):
self.app = app
self.prefix = prefix

def __call__(self, environ, start_response):

if environ['PATH_INFO'].startswith(self.prefix):
environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
environ['SCRIPT_NAME'] = self.prefix
return self.app(environ, start_response)
else:
start_response('404', [('Content-Type', 'text/plain')])
return ["This url does not belong to the app.".encode()]


def create_app():
from project.models import db
from project.views import views_bp as views_blueprint
from project.views.oauth import jwt, bcrypt
environment = os.environ.get("ENVIRONMENT", "default")

app = Flask(__name__)
app.config.from_object(CONFIG[environment])
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=app.config["APPLICATION_ROOT"])

db.init_app(app)
bcrypt.init_app(app)
jwt.init_app(app)

SWAGGER_CONFIG["specs"][0]["route"] = SWAGGER_CONFIG["specs"][0]["route"].format(
application_root=app.config["APPLICATION_ROOT"]
)
SWAGGER_CONFIG["static_url_path"] = SWAGGER_CONFIG["static_url_path"].format(
application_root=app.config["APPLICATION_ROOT"]
)
SWAGGER_CONFIG["specs_route"] = SWAGGER_CONFIG["specs_route"].format(
application_root=app.config["APPLICATION_ROOT"]
)
SWAGGER_CONFIG["basePath"] = SWAGGER_CONFIG["basePath"].format(
application_root=app.config["APPLICATION_ROOT"]
)
Swagger(app, config=SWAGGER_CONFIG)

app.register_blueprint(views_blueprint)
with app.test_request_context():
db.create_all()
return app, db
53 changes: 53 additions & 0 deletions project/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# coding=utf-8
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


class Config:
"""Default configuration for all environments"""

DEBUG = False
TESTING = False
APP_NAME = "Oauth"
APPLICATION_ROOT = "/oauth"
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = os.environ.get("SECRET_KEY") or "gjr39dkjn344_!67#"


class TestConfig(Config):
"""Configuration to run tests"""

DEBUG = True
TESTING = True
DATABASE = os.path.join(BASE_DIR, "db_test.sqlite3")
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, "db_test.sqlite3")


class DevConfig(Config):
"""Configuration to run in local environments"""

DEBUG = True
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, "db.sqlite3")


class PreConfig(Config):
"""Configuration to run with docker and kubernetes in Preproduction"""
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, "db.sqlite3")


class ProdConfig(Config):
"""Configuration to run with docker and kubernetes in Production"""
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, "prod.db")


CONFIG = {
"test": TestConfig,
"dev": DevConfig,
"pre": PreConfig,
"prod": ProdConfig,
"default": DevConfig
}
6 changes: 6 additions & 0 deletions project/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# encoding: utf-8
from __future__ import absolute_import, print_function, unicode_literals

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
31 changes: 31 additions & 0 deletions project/models/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# encoding: utf-8
from __future__ import absolute_import, print_function, unicode_literals

import datetime

from flask_login import UserMixin
from sqlalchemy import Column, Integer, String, Boolean

from project.models import db


class User(db.Model, UserMixin):
__tablename__ = 'auth_user'
date_joined = db.Column(db.DateTime, default=datetime.datetime.utcnow())

id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String, default="")
first_name = Column(String, default="")
last_name = Column(String, default="")
username = Column(String, nullable=False)
password = Column(String, nullable=False)
is_superuser = Column(Boolean, default=False)
is_staff = Column(Boolean, default=False)
is_active = Column(Boolean, default=True)

def __init__(self, username, password):
from project.views.oauth import bcrypt
self.username = username
self.password = bcrypt.generate_password_hash(
password, 13
).decode()
Empty file added project/tests/__init__.py
Empty file.
64 changes: 64 additions & 0 deletions project/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import json
import os
import unittest
from typing import Dict, List, Union, Text

from project import create_app
from project.models.models import User, db

def _format_response(response: Text = "") -> Union[List, Dict]:
return json.loads(response)


class FlaskrTestCase(unittest.TestCase):

def setUp(self):
os.environ["ENVIRONMENT"] = "test"
self.app, self.db = create_app()
self.base_url = self.app.config["APPLICATION_ROOT"]
self.client = self.app.test_client()

def tearDown(self):
os.unlink(self.app.config['DATABASE'])

def _create_user(self, username, password):
with self.app.test_request_context():
user = User(
username=username,
password=password
)
# insert the user
db.session.add(user)
db.session.commit()
return user.id

def test_home(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 404)

def test_protected_view_error(self):
response = self.client.get('{base_url}/check-token'.format(base_url=self.base_url))
self.assertEqual(response.status_code, 401)
self.assertEqual(_format_response(response.data)["description"], "Request does not contain an access token")
self.assertEqual(_format_response(response.data)["error"], "Authorization Required")

def test_login_error(self):
response = self.client.post('{base_url}/login'.format(base_url=self.base_url),
data={"username": "", "password": ""})
self.assertEqual(response.status_code, 401)
self.assertEqual(_format_response(response.data)["message"], 'Bad username and/or password')

def test_login_ok(self):
username = "test"
password = "1234"
self._create_user(username, password)
response = self.client.post('{base_url}/login'.format(base_url=self.base_url),
data={"username": username, "password": password})
self.assertEqual(response.status_code, 200)
result = _format_response(response.data)["access_token"]
self.assertGreater(len(result), 0)

# check protected
response = self.client.get('{base_url}/check-token'.format(base_url=self.base_url),
headers={'authorization': 'JWT {}'.format(result)})
self.assertEqual(response.status_code, 200)
8 changes: 8 additions & 0 deletions project/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import, division

from flask import Blueprint

views_bp = Blueprint('views', __name__, static_url_path='/static')

from project.views import views
22 changes: 22 additions & 0 deletions project/views/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from flask_bcrypt import Bcrypt
from flask_jwt import JWT

from project.models.models import User

bcrypt = Bcrypt()


def authenticate(username, password):
user = User.query.filter_by(username=username).first()
if user and bcrypt.check_password_hash(
user.password, password
):
return user


def identity(payload):
user_id = payload['identity']
return User.query.filter_by(id=user_id).first()


jwt = JWT(authentication_handler=authenticate, identity_handler=identity)
Loading

0 comments on commit 2e587e3

Please sign in to comment.