diff options
-rw-r--r-- | app/__init__.py | 2 | ||||
-rw-r--r-- | app/views.py | 72 | ||||
-rwxr-xr-x | client/bug.py | 20 | ||||
-rwxr-xr-x | client/bug_delete.py | 29 | ||||
-rwxr-xr-x | client/bug_edit.py | 46 | ||||
-rwxr-xr-x | client/bug_list.py | 6 | ||||
-rwxr-xr-x | client/bug_open.py | 64 | ||||
-rwxr-xr-x | client/bug_show.py | 4 | ||||
-rw-r--r-- | config.py | 4 |
9 files changed, 154 insertions, 93 deletions
diff --git a/app/__init__.py b/app/__init__.py index d698823..af83e2c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,7 +3,7 @@ from flask.ext.sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_object('config') -app.secret_key = 'SUPERSEEKRITKEY' +app.secret_key = app.config['SECRET_KEY'] db = SQLAlchemy(app) from app import views, models diff --git a/app/views.py b/app/views.py index acaf3f1..c19b830 100644 --- a/app/views.py +++ b/app/views.py @@ -8,14 +8,9 @@ import json @app.route('/authorized') def authorized_callback(): github = OAuth2Session(app.config['GITHUB_CLIENT_ID'], state=session['oauth_state']) - token = github.fetch_token(app.config['TOKEN_URL'], client_secret=app.config['GITHUB_CLIENT_SECRET'], authorization_response=request.url) - - session['oauth_token'] = token - user_data = github.get('https://api.github.com/user') - if user_data.status_code == 401: abort(401) @@ -23,20 +18,21 @@ def authorized_callback(): user = models.User.query.filter(models.User.id == json_data['id']).first() if not user: user = models.User( - id = json_data['id'], + id = json_data['id'], + name = json_data['name'], nickname = json_data['login'], - email = json_data['email'] + email = json_data['email'], ) db.session.add(user) db.session.commit() + session['oauth_token'] = token return "Your access token is: {}".format(token['access_token']) @app.route('/login') def login(): github = OAuth2Session(app.config['GITHUB_CLIENT_ID']) authorization_url, state = github.authorization_url(app.config['AUTHORIZATION_BASE_URL']) - session['oauth_state'] = state return redirect(authorization_url) @@ -51,24 +47,19 @@ def create_ticket(): if not request.json or not ('summary' and 'body' and 'token') in request.json: abort(400) - token = {"scope": [""], "access_token": request.json['token'], "token_type": "bearer"} - github = OAuth2Session(app.config['GITHUB_CLIENT_ID'], token=token) - user_data = github.get('https://api.github.com/user') - if user_data.status_code == 401: + if not authenticate(request.json['token']): abort(401) - user = models.User.query.get(user_data.json()['id']) - - ticket = models.Ticket(summary=request.json['summary'], - body=request.json['body'], - opened_by=user, - opened_at=datetime.utcnow()) + user = models.User.query.get(user_data.json()['id']) + ticket = models.Ticket(summary = request.json['summary'], + body = request.json['body'], + opened_by = user, + opened_at = datetime.utcnow()) db.session.add(ticket) db.session.commit() - td = ticket_to_dict(ticket) - - return jsonify({'ticket': make_public_ticket(td)}), 201 + public_ticket = make_public_ticket(ticket_to_dict(ticket)) + return jsonify({'ticket': public_ticket}), 201 @app.route('/tbt/api/1.0/ticket/<int:ticket_id>', methods=['GET']) def get_ticket(ticket_id): @@ -76,23 +67,37 @@ def get_ticket(ticket_id): if not ticket: abort(404) - return jsonify({'ticket': make_public_ticket(ticket_to_dict(ticket))}) + public_ticket = make_public_ticket(ticket_to_dict(ticket)) + return jsonify({'ticket': public_ticket}) @app.route('/tbt/api/1.0/ticket/<int:ticket_id>', methods=['PUT']) def update_ticket(ticket_id): - ticket = next((t for t in tickets if t['id'] == ticket_id), None) - if not ticket: - abort(404) + if not 'Access-Token' in request.headers or not authenticate(request.headers['Access-Token']): + abort(401) if not request.json: + print(request.data) abort(400) - ticket['summary'] = request.json.get('summary', ticket['summary']) - ticket['body'] = request.json.get('body', ticket['body']) - ticket['status'] = request.json.get('status', ticket['status']) - ticket['reason'] = request.json.get('reason', ticket['reason']) - return jsonify({'ticket': make_public_ticket(ticket)}) + + ticket = models.Ticket.query.get(ticket_id) + if not ticket: + abort(404) + + ticket.summary = request.json.get('summary', ticket.summary) + ticket.body = request.json.get('body', ticket.body) + ticket.status = request.json.get('status', ticket.status) + ticket.reason = request.json.get('reason', ticket.reason) + ticket.updated_at = datetime.utcnow() + db.session.add(ticket) + db.session.commit() + + public_ticket = make_public_ticket(ticket_to_dict(ticket)) + return jsonify({'ticket': public_ticket}) @app.route('/tbt/api/1.0/ticket/<int:ticket_id>', methods=['DELETE']) def delete_ticket(ticket_id): + if not 'Access-Token' in request.headers or not authenticate(request.headers['Access-Token']): + abort(401) + ticket = models.Ticket.query.get(ticket_id) if not ticket: abort(404) @@ -106,3 +111,10 @@ def delete_ticket(ticket_id): def not_found(error): return make_response(jsonify({'error': 'Not found'}), 404) +@app.errorhandler(401) +def unauthorized(error): + return make_response(jsonify({'error': 'Unauthorized'}), 401) + +@app.errorhandler(400) +def unauthorized(error): + return make_response(jsonify({'error': 'Bad request'}), 400) diff --git a/client/bug.py b/client/bug.py index ff7958f..a5c3814 100755 --- a/client/bug.py +++ b/client/bug.py @@ -24,10 +24,10 @@ See 'bug help <command>' for more information on a specific command from importlib import import_module from subprocess import call from docopt import docopt +import sys commands = ['open', 'delete', 'show', 'list', 'edit'] def main(): - if args['<command>'] in ['help', None]: if not args['<args>']: print(__doc__.lstrip().rstrip()) @@ -36,18 +36,22 @@ def main(): bug_mod = import_module('bug_{}'.format(args['<args>'][0])) print(bug_mod.__doc__.lstrip().rstrip()) else: - exit("'{}' is not a bug.py command. See 'bug help'.".format(args['<args>'][0])) + sys.exit("'{}' is not a bug.py command. See 'bug help'.".format(args['<args>'][0])) + elif args['<command>'] in commands: if not args['--uri']: - exit("URI missing") + sys.exit("URI missing") bug_mod = import_module('bug_{}'.format(args['<command>'])) - argv = [args['<command>']] + args['<args>'] - arguments = args.copy() - arguments.update(docopt(bug_mod.__doc__, argv=argv)) - bug_mod.call(arguments) + + arg = {'--uri': args['--uri']} + arg.update(docopt(bug_mod.__doc__, + argv=[args['<command>']] + args['<args>'])) + + bug_mod.entrypoint(arg) + else: - exit("'{}' is not a bug.py command. See 'bug help'.".format(args['<command>'])) + sys.exit("'{}' is not a bug.py command. See 'bug help'.".format(args['<command>'])) if __name__ == '__main__': args = docopt(__doc__, diff --git a/client/bug_delete.py b/client/bug_delete.py index bcce88e..393225e 100755 --- a/client/bug_delete.py +++ b/client/bug_delete.py @@ -10,24 +10,37 @@ required. -i, --ticket-id ID of the ticket to delete """ from docopt import docopt -import json, requests +import json, requests, sys +import configparser if __name__ == '__main__': print(docopt(__doc__)) -def call(args): +def entrypoint(args): print(args) - api_endpoint = args['--uri'] + '/api/1.0/ticket/' + c = configparser.ConfigParser() + c.read('config') + config = c[args['--uri']] + access_token = config['access_token'] - req = requests.delete(api_endpoint + args['<ticket_id>']) + uri = args['--uri'] + '/api/1.0/ticket/' + args['<ticket_id>'] + headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Access-Token': access_token, + } + + req = requests.delete(uri, headers=headers, verify=False) res = json.loads(req.text) - if req.status_code == 404: - print("Ticket with ID '{}' could not be deleted: {}".format(args['<ticket_id>'], res['error'])) + if req.status_code == (401 or 404): + sys.exit("Ticket with ID '{}' could not be deleted: {}".format(args['<ticket_id>'], res['error'])) + elif req.status_code == 200: - print("Ticket with ID '{}' deleted successfully.".format(args['<ticket_id>'])) + sys.exit("Ticket with ID '{}' deleted successfully.".format(args['<ticket_id>'])) + else: - exit("ALERT ALERT ALERT") + sys.exit("ALERT ALERT ALERT") #print("{} {}\n {}".format(t['id'], t['title'], t['uri'])) diff --git a/client/bug_edit.py b/client/bug_edit.py index 42eca80..a9394e7 100755 --- a/client/bug_edit.py +++ b/client/bug_edit.py @@ -16,36 +16,50 @@ options: -s, --summary STRING A short summary of the bug -b, --body STRING The long description of the bug or the body of the comment + -e, --editor Open and edit the summary and description in your $EDITOR """ +from bug_show import show_ticket from docopt import docopt -import json, requests +import json, requests, sys +import configparser -if __name__ == '__main__': - print(docopt(__doc__)) - -def call(args): +def entrypoint(args): print(args) - api_endpoint = args['--uri'] + '/api/1.0/ticket' + config = configparser.ConfigParser() + config.read('config') + config = config[args['--uri']] + + access_token = config['access_token'] + api_endpoint = args['--uri'] + '/api/1.0/ticket/' + args['<ticket_id>'] + + if not (args['--summary'] or args['--body']): + if '--editor' in args: + sys.exit("EDITOR edit not implemented") + else: + sys.exit("Summary or body needed") ticket = {} if args['--summary']: ticket['summary'] = args['--summary'] - else: - exit("Summary needed, no interactive edit yet") if args['--body']: ticket['body'] = args['--body'] - else: - ticket['body'] = None + print(edit(api_endpoint, ticket, access_token)) + +def edit(api_endpoint, ticket, access_token): headers = { 'Content-Type': 'application/json', - 'Accept': 'text/plain' + 'Access-Token': access_token } - payload = json.dumps(ticket) - - r = requests.post(api_endpoint, data=payload, headers=headers) - t = json.loads(r.text).get('ticket') + payload = json.dumps(ticket) + print(ticket) + req = requests.put(api_endpoint, data=payload, headers=headers, verify=False) + print(req.text) + res = json.loads(req.text) - print("{} {}\n {}".format(t['id'], t['summary'], t['uri'])) + if req.status_code == 200: + return res.get('ticket') + else: + sys.exit("{}: {}".format(req.status_code, res['error'])) diff --git a/client/bug_list.py b/client/bug_list.py index 701b327..87e60d8 100755 --- a/client/bug_list.py +++ b/client/bug_list.py @@ -12,12 +12,12 @@ from docopt import docopt from textwrap import indent from datetime import datetime from bug_show import show_ticket -import json, requests +import json, requests, sys if __name__ == '__main__': print(docopt(__doc__)) -def call(args): +def entrypoint(args): print(args) api_endpoint = args['--uri'] + '/api/1.0/tickets' @@ -25,7 +25,7 @@ def call(args): tickets = json.loads(r.text) if not tickets: - exit("No tickets found.") + sys.exit("No tickets found.") tickets = tickets.get('tickets') diff --git a/client/bug_open.py b/client/bug_open.py index e9ecb19..db15793 100755 --- a/client/bug_open.py +++ b/client/bug_open.py @@ -11,15 +11,22 @@ required. -b, --body STRING The long description of the bug """ import os, re, tempfile, subprocess -from docopt import docopt -import json, requests from bug_show import show_ticket +from docopt import docopt +import json, requests, sys +import configparser -if __name__ == '__main__': - print(docopt(__doc__)) - -def call(args): +def entrypoint(args): print(args) + + editor = os.environ.get('EDITOR', 'vim') + + c = configparser.ConfigParser() + c.read('config') + config = c[args['--uri']] + + access_token = config['access_token'] + api_endpoint = args['--uri'] + '/api/1.0/ticket' if args['--summary']: @@ -33,38 +40,49 @@ def call(args): body = '' if not(summary and body): - (summary, body) = editor_prompt(summary, body) + summary, body = editor_prompt(summary, body) ticket = { 'summary': summary, 'body': body, - 'token': 'TOKENHERE' + 'token': access_token } + open(api_endpoint, summary, ticket) + +def open(api_endpoint, access_token, data): + payload = json.dumps(data) + print(data) + headers = { 'Content-Type': 'application/json', - 'Accept': 'text/plain', + 'Accept': 'application/json', } - payload = json.dumps(ticket) - r = requests.post(api_endpoint, data=payload, headers=headers, verify=False) + req = requests.post(api_endpoint, data=payload, headers=headers, verify=False) - t = json.loads(r.text).get('ticket') + if req.status_code != 201: + print("Ticket not opened. Status code '{}'".format(req.status_code)) + sys.exit(req.text) + else: + response = json.loads(req.text) - print(t) - print(show_ticket(t)) + if 'ticket' in response: + ticket = response.get('ticket') + else: + sys.exit('aborting: ticket not found in response? Something\'s borked') + print(ticket) + print(show_ticket(ticket)) -def editor_prompt(summary, body): - editor = os.environ.get('EDITOR','vim') - message='' +def editor_prompt(editor, summary, body): + regx = re.compile('^(.+?)\n\n(.+)$', re.S) + message='' if summary: message += summary - if body: message += '\n\n' + body - message += """ # Please enter the summary on a single line, followed @@ -80,8 +98,6 @@ def editor_prompt(summary, body): tmp.write(message.encode("utf-8")) tmp.flush() - regx = re.compile('^(.+?)\n\n(.+)$', re.S) - subprocess.call([editor, tmp.name]) tmp.seek(0) @@ -89,13 +105,13 @@ def editor_prompt(summary, body): tmp.close() data = data[:-263] # Strip the commented out message - data = data.lstrip().rstrip() # Strip opening and ending whitespace + data = data.lstrip().rstrip() regmatch = regx.match(data) if len(regmatch.groups()) != 2: - exit("Error: summary and body not separated properly, aborting") + sys.exit("Error: summary and body not separated properly, aborting") summary = regmatch.group(1) body = regmatch.group(2) - return (summary, body) + return summary, body diff --git a/client/bug_show.py b/client/bug_show.py index 18175ef..bd0ff3c 100755 --- a/client/bug_show.py +++ b/client/bug_show.py @@ -12,11 +12,11 @@ import json, requests if __name__ == '__main__': print(docopt(__doc__)) -def call(args): +def entrypoint(args): print(args) api_endpoint = args['--uri'] + '/api/1.0/ticket/' - r = requests.get(api_endpoint + args['<ticket_id>']) + r = requests.get(api_endpoint + args['<ticket_id>'], verify=False) ticket = json.loads(r.text).get('ticket') print(ticket) @@ -1,7 +1,9 @@ import os - basedir = os.path.abspath(os.path.dirname(__file__)) +# Used for CSRF protection, keep secret +SECRET_KEY = 'SUPERSEEKRIT' + SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') |