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

maybe finished? #16

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pycryptodome==3.3.1
pylint==2.3.1
python-jose-cryptodome==1.3.2
six==1.12.0
typed-ast==1.4.2
typed-ast==1.5.4
Werkzeug==0.15.6
wrapt==1.11.1
Flask-Cors==3.0.8
113 changes: 110 additions & 3 deletions Project/03_coffee_shop_full_stack/starter_code/backend/src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
!! NOTE THIS MUST BE UNCOMMENTED ON FIRST RUN
!! Running this funciton will add one
'''
# db_drop_and_create_all()
db_drop_and_create_all()

# ROUTES
'''
Expand All @@ -28,7 +28,19 @@
returns status code 200 and json {"success": True, "drinks": drinks} where drinks is the list of drinks
or appropriate status code indicating reason for failure
'''
@app.route('/drinks')
def get_drinks():
drinks = Drink.query.all()

if len(drinks) == 0:
abort(404)

return jsonify(
{
'success': True,
'drinks': [drink.short() for drink in drinks],
}
)

'''
@TODO implement endpoint
Expand All @@ -38,7 +50,20 @@
returns status code 200 and json {"success": True, "drinks": drinks} where drinks is the list of drinks
or appropriate status code indicating reason for failure
'''
@app.route('/drinks-detail')
@requires_auth('get:drinks-detail')
def get_drinks_detail(permission):
drinks = Drink.query.all()

if len(drinks) == 0:
abort(404)

return jsonify(
{
'success': True,
'drinks': [drink.long() for drink in drinks],
}
)

'''
@TODO implement endpoint
Expand All @@ -49,7 +74,29 @@
returns status code 200 and json {"success": True, "drinks": drink} where drink an array containing only the newly created drink
or appropriate status code indicating reason for failure
'''
@app.route("/drinks", methods=["POST"])
@requires_auth('post:drinks')
def create_drink(token):
body = request.get_json()

try:

drink = Drink(
title = body.get('title', None),
recipe = json.dumps(body.get('recipe', None))
)

drink.insert()

except:
abort(422)

return jsonify(
{
'success': True,
'drinks': [drink.long()]
}
)

'''
@TODO implement endpoint
Expand All @@ -62,7 +109,37 @@
returns status code 200 and json {"success": True, "drinks": drink} where drink an array containing only the updated drink
or appropriate status code indicating reason for failure
'''
@app.route("/drinks/<int:id>", methods=["PATCH"])
@requires_auth('patch:drinks')
def update_drink(token, id):
drink = Drink.query.filter(Drink.id == id).one_or_none()

if drink is None:
abort(404)

body = request.get_json()

req_title = body.get('title', None)
req_recipe = body.get('recipe', None)

try:
if req_title is not None:
drink.title = req_title
drink.update()

if req_recipe is not None:
drink.recipe = json.dumps(req_recipe)
drink.update()

except Exception:
abort(422)

return jsonify(
{
'success': True,
'drinks': [drink.long()]
}
)

'''
@TODO implement endpoint
Expand All @@ -74,7 +151,25 @@
returns status code 200 and json {"success": True, "delete": id} where id is the id of the deleted record
or appropriate status code indicating reason for failure
'''
@app.route("/drinks/<int:id>", methods=["DELETE"])
@requires_auth('delete:drinks')
def delete_drink(token, id):
drink = Drink.query.filter(Drink.id == id).one_or_none()

if drink is None:
abort(404)

try:
drink.delete()
except Exception:
abort(422)

return jsonify(
{
'success': True,
'delete': id
}
)

# Error Handling
'''
Expand All @@ -99,16 +194,28 @@ def unprocessable(error):
"error": 404,
"message": "resource not found"
}), 404

'''

'''
@TODO implement error handler for 404
error handler should conform to general task above
'''

@app.errorhandler(404)
def not_found(error):
return jsonify({
"success": False,
"error": 404,
"message": "resource not found"
}), 404

'''
@TODO implement error handler for AuthError
error handler should conform to general task above
'''
@app.errorhandler(AuthError)
def auth_error(e):
return jsonify({
"success": False,
"error": e.status_code,
"message": e.error['description']
}), e.status_code
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import json
from flask import request, _request_ctx_stack
from flask import abort, request, _request_ctx_stack
from functools import wraps
from jose import jwt
from urllib.request import urlopen


AUTH0_DOMAIN = 'udacity-fsnd.auth0.com'
AUTH0_DOMAIN = 'dev-7wl0xelsvzknetbq.us.auth0.com'
ALGORITHMS = ['RS256']
API_AUDIENCE = 'dev'
API_AUDIENCE = 'coffeeShop'

## AuthError Exception
'''
Expand All @@ -31,7 +31,32 @@ def __init__(self, error, status_code):
return the token part of the header
'''
def get_token_auth_header():
raise Exception('Not Implemented')
auth = request.headers.get('Authorization', None)
if not auth:
raise AuthError({
'code': 'authorization_header_missing',
'description': 'Authorization header is expected here.'
}, 401)
parts = auth.split()
if parts[0].lower() != 'bearer':
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must start with "Bearer".'
}, 401)

elif len(parts) == 1:
raise AuthError({
'code': 'invalid_header',
'description': 'Token can not be found.'
}, 401)

elif len(parts) > 2:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must be a bearer token.'
}, 401)
token = parts[1]
return token

'''
@TODO implement check_permissions(permission, payload) method
Expand All @@ -45,7 +70,17 @@ def get_token_auth_header():
return true otherwise
'''
def check_permissions(permission, payload):
raise Exception('Not Implemented')
if 'permissions' not in payload:
raise AuthError({
'code': 'invalid_claims',
'description': 'Permissions is not included in JWT.'
}, 400)
if permission not in payload['permissions']:
raise AuthError({
'code': 'unauthorized',
'description': 'Permission is not found here.'
}, 403)
return True

'''
@TODO implement verify_decode_jwt(token) method
Expand All @@ -61,7 +96,55 @@ def check_permissions(permission, payload):
!!NOTE urlopen has a common certificate error described here: https://stackoverflow.com/questions/50236117/scraping-ssl-certificate-verify-failed-error-for-http-en-wikipedia-org
'''
def verify_decode_jwt(token):
raise Exception('Not Implemented')
jsonurl = urlopen(f'https://{AUTH0_DOMAIN}/.well-known/jwks.json')
jwks = json.loads(jsonurl.read())
unverified_header = jwt.get_unverified_header(token)
rsa_key = {}
if 'kid' not in unverified_header:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization is malformed.'
}, 401)
for key in jwks['keys']:
if key['kid'] == unverified_header['kid']:
rsa_key = {
'kty': key['kty'],
'kid': key['kid'],
'use': key['use'],
'n': key['n'],
'e': key['e']
}
if rsa_key:
try:
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer='https://' + 'AUTH0_DOMAIN' + '/'
)
return payload

except jwt.ExpiredSignatureError:
raise AuthError({
'code': 'token_expired',
'description': 'Token is expired.'
}, 401)

except jwt.JWTClaimsError:
raise AuthError({
'code': 'invalid_claims',
'description': 'Incorrect claims. Please, check the audience and issuer.'
}, 401)
except Exception:
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to parse authentication token.'
}, 400)
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to find the appropriate key.'
}, 400)

'''
@TODO implement @requires_auth(permission) decorator method
Expand All @@ -78,9 +161,11 @@ def requires_auth_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
token = get_token_auth_header()
payload = verify_decode_jwt(token)
try:
payload = verify_decode_jwt(token)
except:
abort(401)
check_permissions(permission, payload)
return f(payload, *args, **kwargs)

return wrapper
return requires_auth_decorator
return requires_auth_decorator
Loading