Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
171 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,23 @@ | ||
from __future__ import absolute_import | ||
|
||
from flask import Blueprint | ||
from flask import jsonify | ||
from flask import url_for | ||
|
||
from .auth import requires_auth | ||
from . import v1 | ||
|
||
api = Blueprint('api', __name__) | ||
|
||
@api.route('/') | ||
@requires_auth | ||
def index(): | ||
return jsonify( | ||
"description"="REST API for CT", | ||
"version"="1.0" | ||
) | ||
return jsonify({ | ||
"description": "REST API for CT", | ||
"links": [{ | ||
"rel": "api-version-1.0", | ||
"href": url_for(".v1_0.index") | ||
}] | ||
}) | ||
|
||
v1.add_routes(api) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from __future__ import absolute_import | ||
|
||
from functools import wraps | ||
from hashlib import sha256 | ||
from flask import request, Response | ||
from flask import session, g | ||
|
||
from ct.core.apis import BaseAPI | ||
|
||
|
||
def get_ct_object(username, password): | ||
key = get_session_key(username, password) | ||
if key in session and session[key].valid_session(): | ||
return session[key] | ||
|
||
if do_ct_login(username, password): | ||
return get_ct_object(username, password) | ||
|
||
return None | ||
|
||
|
||
def do_ct_login(username, password): | ||
ct = BaseAPI("https://currenttime.bouvet.no") | ||
logged_in = ct.login(username, password) | ||
if logged_in: | ||
key = get_session_key(username, password) | ||
session[key] = ct | ||
|
||
return logged_in | ||
|
||
|
||
def get_session_key(username, password): | ||
return sha256("%s:%s" % (username, password)).hexdigest() | ||
|
||
|
||
def check_auth(username, password): | ||
"""This function is called to check if a username / | ||
password combination is valid. | ||
""" | ||
return get_ct_object(username, password) is not None | ||
|
||
|
||
def get_auth_headers(): | ||
"""Returns the WWW-Authenticate headers. We use Basic unless the | ||
clients has set the UseXBasic header. In that case we use XBasic | ||
instead. This is because most web browsers insist on showing the | ||
Basic auth dialogue even if the request is done using XHR.""" | ||
|
||
auth_type = "Basic" | ||
if request.headers.get('UseXBasic'): | ||
auth_type = "XBasic" | ||
|
||
return { | ||
'WWW-Authenticate': '%s realm="Login Required"' % auth_type | ||
} | ||
|
||
|
||
def authenticate(): | ||
"""Sends a 401 response that enables basic auth""" | ||
|
||
return Response( | ||
'Could not verify your access level for that URL.\n' | ||
'You have to login with proper credentials', 401, | ||
get_auth_headers()) | ||
|
||
|
||
def requires_auth(f): | ||
@wraps(f) | ||
def decorated(*args, **kwargs): | ||
auth = request.authorization | ||
if not auth or not check_auth(auth.username, auth.password): | ||
return authenticate() | ||
|
||
g.ct = get_ct_object(auth.username, auth.password) | ||
|
||
return f(*args, **kwargs) | ||
return decorated |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from __future__ import absolute_import | ||
|
||
from .auth import requires_auth | ||
from flask import jsonify | ||
from flask import url_for | ||
from flask import g | ||
|
||
|
||
@requires_auth | ||
def index(): | ||
return jsonify({ | ||
"description": "REST API for CT version 1.0", | ||
"links": [{ | ||
"rel": "available-projects", | ||
"href": url_for('.projects') | ||
}, { | ||
"rel": "activities-by-week", | ||
"href": url_for('.week', year="<year>", week="<week>") | ||
}] | ||
}) | ||
|
||
|
||
def serialize_projects(projects): | ||
result = [] | ||
for p in projects: | ||
result.append({ | ||
'id': p.id, | ||
'name': p.name, | ||
'project_name': p.project_name, | ||
'task_name': p.task_name, | ||
'subtask_name': p.subtask_name, | ||
'activity_name': p.activity_name, | ||
}) | ||
return result | ||
|
||
|
||
def serialize_activities(activities): | ||
result = [] | ||
for activity in activities: | ||
if activity.duration <= 0: | ||
continue | ||
|
||
date = activity.day.strftime("%Y-%m-%d") | ||
result.append({ | ||
'id': activity.project_id, | ||
'comment': activity.comment, | ||
'duration': str(activity.duration), | ||
'day': date | ||
}) | ||
return result | ||
|
||
|
||
@requires_auth | ||
def get_projects(): | ||
projects = serialize_projects(g.ct.get_projects()) | ||
|
||
return jsonify({ | ||
"projects": projects | ||
}) | ||
|
||
|
||
@requires_auth | ||
def get_week(year, week): | ||
activities = serialize_activities(g.ct.get_week(year, week)) | ||
|
||
return jsonify({ | ||
"activities": activities | ||
}) | ||
|
||
|
||
def add_routes(api): | ||
root = 'v1_0' | ||
api.add_url_rule('/v1/', | ||
root + '.index', index) | ||
api.add_url_rule('/v1/projects/', | ||
root + '.projects', get_projects) | ||
api.add_url_rule('/v1/week/<int:year>/<int:week>', | ||
root + '.week', get_week) |