From 441fa7e6b09d3b462bbe38b9a5630836fc5fbc36 Mon Sep 17 00:00:00 2001 From: Will Oursler Date: Tue, 22 Jul 2014 21:48:14 -0700 Subject: [PATCH 1/4] Adds basics of groups. Exposes read interface, but it is primative and there is no edit interface (that will have to wait for better auth solutions, to a degree). --- src/dispatch.py | 2 ++ src/groups/__init__.py | 1 + src/groups/groups.py | 22 +++++++++++++++++++ .../authorization_core/authorization_core.py | 6 +++++ 4 files changed, 31 insertions(+) create mode 100644 src/groups/__init__.py create mode 100644 src/groups/groups.py diff --git a/src/dispatch.py b/src/dispatch.py index 220c8d2..c89bf3e 100644 --- a/src/dispatch.py +++ b/src/dispatch.py @@ -9,6 +9,7 @@ from profile import app as profile_app from rooming_assignment import app as rooming_assignment_app from rooms import app as rooms_app +from groups import app as groups_app app = Flask(__name__) @@ -23,6 +24,7 @@ '/profile': profile_app, # TODO: rename to profiles? '/rooming_assignment': rooming_assignment_app, # TODO: rename to rooming_assignments? '/rooms': rooms_app, + '/groups': groups_app, }) app.run() diff --git a/src/groups/__init__.py b/src/groups/__init__.py new file mode 100644 index 0000000..5d2cbc8 --- /dev/null +++ b/src/groups/__init__.py @@ -0,0 +1 @@ +from groups import * diff --git a/src/groups/groups.py b/src/groups/groups.py new file mode 100644 index 0000000..06ccb02 --- /dev/null +++ b/src/groups/groups.py @@ -0,0 +1,22 @@ +#!/usr/bin/python + +# Setup flask basics. +from flask import Flask, jsonify +from ..utils import authorization_core as authcore + +app = Flask(__name__) + +@app.route('/') +def serve_groups(): + print authcore.get_groupnames() + return jsonify(groupnames = authcore.get_groupnames()) + +@app.route('//') +def serve_room( groupname ): + return jsonify( + groupname = groupname, + method = list(authcore.members(groupname))) + +if __name__ == "__main__": + app.debug = True # TODO: Remove in production. + app.run() diff --git a/src/utils/authorization_core/authorization_core.py b/src/utils/authorization_core/authorization_core.py index d0e00b7..fe4ded0 100644 --- a/src/utils/authorization_core/authorization_core.py +++ b/src/utils/authorization_core/authorization_core.py @@ -20,6 +20,12 @@ # Local imports from authorization_exceptions import * +def get_groupnames(): + group_db = db.init('group') + groups = [] + return [ group.groupname for group in group_db.query(db.Group) ] + # TODO: Close db? + def is_group( name ): group_db = db.init('group') group = group_db.query(db.Group).get( name ) From 20ab0c2d2de42d04ec5b3288ed3652da4c35a18d Mon Sep 17 00:00:00 2001 From: Will Oursler Date: Sun, 27 Jul 2014 10:59:44 -0700 Subject: [PATCH 2/4] Intial login commit. Should allow us to develop authenticated methods and applications while adding SSO later. There are still huge gaping security holes here, but the eternally facing API should be stable. --- src/.gitignore | 3 + src/auth/__init__.py | 1 - src/auth/auth.py | 64 ----------- src/auth/templates/logged_in.html | 3 - src/auth/templates/logged_out.html | 3 - src/auth/templates/test.html | 6 -- src/dispatch.py | 4 +- src/login/__init__.py | 1 + src/login/login.py | 102 ++++++++++++++++++ src/{auth => login}/templates/login.html | 3 +- .../authentication_core.py | 1 - src/utils/db/db/user/user.db | Bin 5120 -> 5120 bytes stubgen/apis.yaml | 8 ++ stubgen/stubs/javascript/simmons-api.js | 10 ++ stubgen/stubs/python/simmons_api/__init__.py | 1 + stubgen/stubs/python/simmons_api/login.py | 20 ++++ 16 files changed, 148 insertions(+), 82 deletions(-) create mode 100644 src/.gitignore delete mode 100644 src/auth/__init__.py delete mode 100644 src/auth/auth.py delete mode 100644 src/auth/templates/logged_in.html delete mode 100644 src/auth/templates/logged_out.html delete mode 100644 src/auth/templates/test.html create mode 100644 src/login/__init__.py create mode 100644 src/login/login.py rename src/{auth => login}/templates/login.html (66%) create mode 100644 stubgen/stubs/python/simmons_api/login.py diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..04eab79 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,3 @@ +password +secret +secret.json diff --git a/src/auth/__init__.py b/src/auth/__init__.py deleted file mode 100644 index 4c06cdc..0000000 --- a/src/auth/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from auth import * diff --git a/src/auth/auth.py b/src/auth/auth.py deleted file mode 100644 index f85a724..0000000 --- a/src/auth/auth.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python - -from ..utils import authentication_core #as authentication -from ..utils import authorization_core #as authorization - -# Setup flask basics. -from flask import Flask, render_template, make_response, request -app = Flask(__name__) - -# TODO: Remove in production? DO NOT PRINT TOKEN FOR FUCKS SAKE. -@app.route('/') -def test(): - username = request.cookies.get('username') - token = request.cookies.get('token') - return render_template( 'test.html', username = username, token = token ) - -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] - try: - token = authentication_core.authenticate( username, password ) - resp = make_response( render_template( 'logged_in.html', username = username ) ) - resp.set_cookie( 'username', username ) - resp.set_cookie( 'token', token ) - return resp - except authentication_core.AuthenticationError: - return "Authentication Error" - else: - return render_template( 'login.html' ) - -@app.route('/logout', methods=['GET', 'POST']) -def logout(): - username = request.cookies.get('username') - token = request.cookies.get('token') - try: - authentication_core.invalidate_token( username, token ) - resp = make_response( render_template( 'logged_out.html', username = username ) ) - resp.set_cookie( 'username', '' ) - resp.set_cookie( 'token', '' ) - return resp - except authentication_core.AuthenticationError: - return "Authentication Error" - -# TODO: Remove in production. -@app.route('/authtest') -def authtest(): - username = request.cookies.get('username') - token = request.cookies.get('token') - - @authorization_core.restricted( "simmons-tech" ) - def super_secret(): - return "Welcome, Simmons Tech Member " + username + "!" - - try: - return super_secret( username, token ) - except authorization_core.AuthorizationError: - return "Nice try " + username + ". No bits for you." - - -if __name__ == "__main__": - app.debug = True # TODO: Remove in production. - app.run() diff --git a/src/auth/templates/logged_in.html b/src/auth/templates/logged_in.html deleted file mode 100644 index 6d00669..0000000 --- a/src/auth/templates/logged_in.html +++ /dev/null @@ -1,3 +0,0 @@ -You've been logged in {{ username }}!
- -testpage diff --git a/src/auth/templates/logged_out.html b/src/auth/templates/logged_out.html deleted file mode 100644 index 599c9b1..0000000 --- a/src/auth/templates/logged_out.html +++ /dev/null @@ -1,3 +0,0 @@ -You've been logged out {{ username }}!
- -testpage diff --git a/src/auth/templates/test.html b/src/auth/templates/test.html deleted file mode 100644 index 9d8aa87..0000000 --- a/src/auth/templates/test.html +++ /dev/null @@ -1,6 +0,0 @@ -Username: {{ username }}
- -Token: {{ token }}
- -login -logout diff --git a/src/dispatch.py b/src/dispatch.py index c89bf3e..bd26380 100644 --- a/src/dispatch.py +++ b/src/dispatch.py @@ -1,7 +1,7 @@ from flask import Flask from werkzeug.wsgi import DispatcherMiddleware -from auth import app as auth_app +from login import app as login_app from buses import app as buses_app from laundry import app as laundry_app from packages import app as packages_app @@ -16,7 +16,7 @@ app.wsgi_app = DispatcherMiddleware( app.wsgi_app, { - '/auth': auth_app, + '/login': login_app, '/buses': buses_app, '/laundry': laundry_app, '/packages': packages_app, diff --git a/src/login/__init__.py b/src/login/__init__.py new file mode 100644 index 0000000..3a6230b --- /dev/null +++ b/src/login/__init__.py @@ -0,0 +1 @@ +from login import * diff --git a/src/login/login.py b/src/login/login.py new file mode 100644 index 0000000..d4b8a96 --- /dev/null +++ b/src/login/login.py @@ -0,0 +1,102 @@ +#!/usr/bin/python + +from ..utils import authentication_core +from ..utils import authorization_core + +import binascii +import os + +# Setup flask basics. +from flask import Flask, render_template, make_response, request, jsonify, redirect +app = Flask(__name__) + +### SESSION LOOKUP AND STORE ### + +# TODO: Do not use an in memory store. Make a LOGIN_SESSION Database. + +session_cache = {} + +def register_session(redirect, state, domain): + session_id = binascii.hexlify(os.urandom(16)) # Hex Encoding for URL Saftey. + session_cache[session_id] = (redirect, state, domain) + print session_id, redirect, state, domain + return session_id + +# Deletes the session and returns it. +def recall_session( session_id ): + session_id = session_id.strip() + # TODO: Delete to preserve memory... + return session_cache[session_id] + +################################ + +def domain_from_redirect( redirect ): + #TODO: Use URL Parse. + if 'mit.edu' in redirect: + return '.mit.edu' + return '' + +@app.route('/') +def login_page(): + # Required args. + redirect = request.args.get('redirect', None) + if redirect == None: + return "500: Must Provide Redirect (i.e. login/?redirect=google.com)" + # Optional args. + state = request.args.get('state', '') + domain = request.args.get('domain', domain_from_redirect(redirect)) + + # TODO: Generate session key? Don't expose redirect, state, domain. + session_id = register_session(redirect, state, domain) + + # Lot of work in login.html. + return render_template( 'login.html', session_id = session_id ) + +@app.route('/handler', methods=['POST']) +def login_handler(): + # TODO: This is only the local case, reflect that. + session_id = request.args.get('session_id') + username = request.form['username'] + password = request.form['password'] # TODO: This is horribly insecure... Use a burner key with SRP. + redirect_link, state, domain = recall_session( session_id ) # TODO: Handle case where session_id not in cache. + print 'Entering try...' + try: + token = authentication_core.authenticate( username, password ) + response = make_response(redirect(redirect_link)) + response.set_cookie( 'username', username ) + response.set_cookie( 'token', token ) + return response + except authentication_core.AuthenticationError: + return "500: Authentication Error" + +# TODO: Add redirect to this, default to login page. +@app.route('/invalidate', methods=['GET','POST']) +def invalidate_token(): + username = request.cookies.get('username') + token = request.cookies.get('token') + redirect_link = request.args.get('redirect', '/login/?redirect=http://simmons.mit.edu') + print username, token, redirect_link + print 'A' + try: + authentication_core.invalidate_token( username, token ) + print 'B' + return make_response(redirect(redirect_link)) + except authentication_core.AuthenticationError: + return "500: Authentication Error" + +@app.route('/check') +def check_token(): + try: + username = request.cookies.get('username') + token = request.cookies.get('token') + print 'A' + + authentication_core.validate_token(username, token) + print 'C' + return jsonify(response='200', username=username) + except: # TODO: Restrict what this catches. + return jsonify(response='401') + +if __name__ == "__main__": + app.debug = True # TODO: Remove in production. + app.run() diff --git a/src/auth/templates/login.html b/src/login/templates/login.html similarity index 66% rename from src/auth/templates/login.html rename to src/login/templates/login.html index 692962e..b180927 100644 --- a/src/auth/templates/login.html +++ b/src/login/templates/login.html @@ -1,6 +1,5 @@ -
+ Username:
Password:

-testpage diff --git a/src/utils/authentication_core/authentication_core.py b/src/utils/authentication_core/authentication_core.py index e3814f9..028f5a1 100644 --- a/src/utils/authentication_core/authentication_core.py +++ b/src/utils/authentication_core/authentication_core.py @@ -16,7 +16,6 @@ # Util imports from .. import db - # Authcore imports import HMAC from authentication_exceptions import * diff --git a/src/utils/db/db/user/user.db b/src/utils/db/db/user/user.db index 99fe14a47669a292663b68e482820695317d4c02..8ba97af251feb1c129f963026393db058c1c623f 100644 GIT binary patch delta 65 zcmV-H0KWf#D1a!C8v&A$976$;v0$MK4Fcr=0|2c60|f!c0IiX6pR=tDI06I!001D7 XK^zEycmW3&feiqY?G8t?R1d5KbK(+` delta 90 zcmV-g0HyzcD1a!C8v%Ba976$iv0$MK4FCWD0|2c60_6e60IiX6pR=tDI06WQ7y${r~^~ diff --git a/stubgen/apis.yaml b/stubgen/apis.yaml index 840e916..6700b0b 100644 --- a/stubgen/apis.yaml +++ b/stubgen/apis.yaml @@ -2,6 +2,14 @@ # This method is used to generate stubs for both local and remote use on both clients and providers. --- +- name: login + desc: Aux. methods for supporting the Simmons SSO System. + path: "login/" + fxns: + - name: check + desc: Checks if the user is logged in. If so, will provide their username. + args: [] + path: "check" - name: rooms desc: Provides data about the physical characteristics of Simmons rooms. path: "rooms/" diff --git a/stubgen/stubs/javascript/simmons-api.js b/stubgen/stubs/javascript/simmons-api.js index b72f53f..3d57417 100644 --- a/stubgen/stubs/javascript/simmons-api.js +++ b/stubgen/stubs/javascript/simmons-api.js @@ -11,6 +11,16 @@ this.RPC_call = function( path, callback ) { } +// Beginning stubs for login: +// Aux. methods for supporting the Simmons SSO System. +this.login = { + + // Checks if the user is logged in. If so, will provide their username. + check: function( callback ) { + return RPC_call( "login/check", callback ); + }, +}; // End of stubs for login + // Beginning stubs for rooms: // Provides data about the physical characteristics of Simmons rooms. this.rooms = { diff --git a/stubgen/stubs/python/simmons_api/__init__.py b/stubgen/stubs/python/simmons_api/__init__.py index c79666d..6d11534 100644 --- a/stubgen/stubs/python/simmons_api/__init__.py +++ b/stubgen/stubs/python/simmons_api/__init__.py @@ -4,6 +4,7 @@ # This will ensure that changes are reflected in other # languages stubs. +import login import rooms import rooming_assignment import people diff --git a/stubgen/stubs/python/simmons_api/login.py b/stubgen/stubs/python/simmons_api/login.py new file mode 100644 index 0000000..9b521bb --- /dev/null +++ b/stubgen/stubs/python/simmons_api/login.py @@ -0,0 +1,20 @@ +# SIMMONS API CLIENT STUBS FOR PYTHON +# This code was auto-generated by stubgen.py +# DO NOT EDIT IT BY HAND. Edit apis.yaml instead. +# This will ensure that changes are reflected in other +# languages stubs. + +from __common import * + +### +# Beginning stubs for login: +# Aux. methods for supporting the Simmons SSO System. +### + +# Checks if the user is logged in. If so, will provide their username. +def check( ): + return RPC_call( "login/check" ) + +### +# End of stubs for login +### \ No newline at end of file From 436756f3e47d34833a0ce38f2492a42a4b4ce297 Mon Sep 17 00:00:00 2001 From: Will Oursler Date: Wed, 30 Jul 2014 17:28:02 -0700 Subject: [PATCH 3/4] Remove debug statements --- src/login/login.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/login/login.py b/src/login/login.py index d4b8a96..e105d8e 100644 --- a/src/login/login.py +++ b/src/login/login.py @@ -19,7 +19,6 @@ def register_session(redirect, state, domain): session_id = binascii.hexlify(os.urandom(16)) # Hex Encoding for URL Saftey. session_cache[session_id] = (redirect, state, domain) - print session_id, redirect, state, domain return session_id # Deletes the session and returns it. @@ -59,7 +58,6 @@ def login_handler(): username = request.form['username'] password = request.form['password'] # TODO: This is horribly insecure... Use a burner key with SRP. redirect_link, state, domain = recall_session( session_id ) # TODO: Handle case where session_id not in cache. - print 'Entering try...' try: token = authentication_core.authenticate( username, password ) response = make_response(redirect(redirect_link)) @@ -75,11 +73,8 @@ def invalidate_token(): username = request.cookies.get('username') token = request.cookies.get('token') redirect_link = request.args.get('redirect', '/login/?redirect=http://simmons.mit.edu') - print username, token, redirect_link - print 'A' try: authentication_core.invalidate_token( username, token ) - print 'B' return make_response(redirect(redirect_link)) except authentication_core.AuthenticationError: return "500: Authentication Error" @@ -89,10 +84,7 @@ def check_token(): try: username = request.cookies.get('username') token = request.cookies.get('token') - print 'A' - authentication_core.validate_token(username, token) - print 'C' return jsonify(response='200', username=username) except: # TODO: Restrict what this catches. return jsonify(response='401') From f2c065bdc38bf25557ba6b71b1b706b03594ee76 Mon Sep 17 00:00:00 2001 From: Will Oursler Date: Fri, 1 Aug 2014 09:44:56 -0700 Subject: [PATCH 4/4] Address Allen's comments (except ones that need SRP to fix). --- src/groups/groups.py | 7 +++---- src/login/login.py | 17 +++++++++-------- .../authorization_core/authorization_core.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/groups/groups.py b/src/groups/groups.py index 06ccb02..b9ba5b6 100644 --- a/src/groups/groups.py +++ b/src/groups/groups.py @@ -8,15 +8,14 @@ @app.route('/') def serve_groups(): - print authcore.get_groupnames() return jsonify(groupnames = authcore.get_groupnames()) @app.route('//') -def serve_room( groupname ): +def serve_members( groupname ): return jsonify( groupname = groupname, - method = list(authcore.members(groupname))) + members = list(authcore.members(groupname))) if __name__ == "__main__": app.debug = True # TODO: Remove in production. - app.run() + app.run() diff --git a/src/login/login.py b/src/login/login.py index e105d8e..c9d618b 100644 --- a/src/login/login.py +++ b/src/login/login.py @@ -23,9 +23,9 @@ def register_session(redirect, state, domain): # Deletes the session and returns it. def recall_session( session_id ): - session_id = session_id.strip() - # TODO: Delete to preserve memory... - return session_cache[session_id] + result = session_cache[session_id] + del session_cache[session_id] + return result ################################ @@ -38,11 +38,11 @@ def domain_from_redirect( redirect ): @app.route('/') def login_page(): # Required args. - redirect = request.args.get('redirect', None) + redirect = request.args.get('redirect', None) if redirect == None: - return "500: Must Provide Redirect (i.e. login/?redirect=google.com)" + return "500: Must Provide Redirect (i.e. login/?redirect=simmons.mit.edu/directory)" # Optional args. - state = request.args.get('state', '') + state = request.args.get('state', '') domain = request.args.get('domain', domain_from_redirect(redirect)) # TODO: Generate session key? Don't expose redirect, state, domain. @@ -55,6 +55,7 @@ def login_page(): def login_handler(): # TODO: This is only the local case, reflect that. session_id = request.args.get('session_id') + session_id = session_id.strip() # TODO: Check if this is needed. It shouldn't be, but I'm paranoid about trailing newlines or something. username = request.form['username'] password = request.form['password'] # TODO: This is horribly insecure... Use a burner key with SRP. redirect_link, state, domain = recall_session( session_id ) # TODO: Handle case where session_id not in cache. @@ -72,7 +73,7 @@ def login_handler(): def invalidate_token(): username = request.cookies.get('username') token = request.cookies.get('token') - redirect_link = request.args.get('redirect', '/login/?redirect=http://simmons.mit.edu') + redirect_link = request.args.get('redirect', '/login/?redirect=http://simmons.mit.edu') try: authentication_core.invalidate_token( username, token ) return make_response(redirect(redirect_link)) @@ -91,4 +92,4 @@ def check_token(): if __name__ == "__main__": app.debug = True # TODO: Remove in production. - app.run() + app.run() diff --git a/src/utils/authorization_core/authorization_core.py b/src/utils/authorization_core/authorization_core.py index fe4ded0..7d7b02f 100644 --- a/src/utils/authorization_core/authorization_core.py +++ b/src/utils/authorization_core/authorization_core.py @@ -22,8 +22,8 @@ def get_groupnames(): group_db = db.init('group') - groups = [] - return [ group.groupname for group in group_db.query(db.Group) ] + groups = [ group.groupname for group in group_db.query(db.Group) ] + return groups # TODO: Close db? def is_group( name ):