summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/__init__.py2
-rw-r--r--app/views.py72
-rwxr-xr-xclient/bug.py20
-rwxr-xr-xclient/bug_delete.py29
-rwxr-xr-xclient/bug_edit.py46
-rwxr-xr-xclient/bug_list.py6
-rwxr-xr-xclient/bug_open.py64
-rwxr-xr-xclient/bug_show.py4
-rw-r--r--config.py4
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)
diff --git a/config.py b/config.py
index f27b5fc..8650ffd 100644
--- a/config.py
+++ b/config.py
@@ -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')