From c101b17f374af74aa97e09063b00e4904912f6c2 Mon Sep 17 00:00:00 2001 From: Oliver Marks Date: Thu, 10 Dec 2015 13:52:58 +0000 Subject: [PATCH] lots of new pages and changes and start of membership system --- Dockerfile | 2 +- README.md | 10 + requirements.txt | 1 + site/authorize.py | 371 +++++++ site/config/settings.py | 81 ++ site/constants.py | 11 +- site/data/__init__.py | 0 site/data/equipment.py | 45 + site/data/members.py | 21 + site/data/profile.py | 19 + site/data/site_user.py | 81 ++ site/data/sql/get_user_credentials.sql | 1 + site/data/sql/get_user_detail.sql | 4 + site/data/sql/get_user_password_reset.sql | 1 + site/data/sql/get_users.sql | 1 + site/data/sql/member_list.sql | 3 + site/generate.py | 3 +- site/html/blog.html | 118 +++ site/html/competition.html | 20 +- site/html/examples.html | 99 -- site/html/index.html | 135 +++ site/index.py | 54 +- site/libs/mail.py | 26 + site/libs/rss_fetcher.py | 14 +- site/libs/rss_invalid.xml | 1 + site/libs/rss_no_tags.xml | 1 + site/migrate.py | 16 + site/pages/__init__.py | 54 +- site/pages/calendar.py | 20 + site/pages/equipment.py | 111 ++ site/pages/examples.py | 17 + site/pages/google_groups.py | 31 + site/pages/homepage.py | 13 +- site/pages/members.py | 54 + site/pages/profile.py | 98 ++ .../migrations/generated_column_indexes.sql | 22 + site/sql/migrations/generated_columns.sql | 69 ++ site/sql/migrations/generated_tables.sql | 4 + site/static/{template => css}/default.css | 74 +- site/static/css/sprite-action-white.css | 982 ++++++++++++++++++ site/static/css/sprite-content-white.css | 208 ++++ site/static/css/sprite-navigation-white.css | 130 +++ .../{template => }/images/background.jpg | Bin .../{template => }/images/background.png | Bin site/static/images/banners/audio_board.jpg | Bin 0 -> 42839 bytes .../images/banners/hackspace-banner.png | Bin 0 -> 21121 bytes site/static/images/banners/microscope.jpg | Bin 0 -> 27842 bytes .../images/banners/object_avoiding_robot.jpg | Bin 0 -> 48416 bytes site/static/images/banners/rocket_camera.jpg | Bin 0 -> 30192 bytes .../static/images/css/sprite-action-white.png | Bin 0 -> 53086 bytes .../images/css/sprite-content-white.png | Bin 0 -> 10702 bytes .../images/css/sprite-navigation-white.png | Bin 0 -> 5263 bytes .../{template => }/images/example-01.jpg | Bin .../{template => }/images/example-02.jpg | Bin .../{template => }/images/example-03.jpg | Bin site/static/images/hackspace-banner.png | Bin 0 -> 21121 bytes .../images/hackspace-banner.svg | 0 .../{template => }/images/hackspace.png | Bin .../{template => }/images/hackspace.svg | 0 site/static/images/hackspace_960x540.png | Bin 0 -> 24981 bytes site/static/images/hackspace_960x960.png | Bin 0 -> 33656 bytes site/static/{template => }/images/icon.png | Bin site/static/images/oauth/google.png | Bin 0 -> 4189 bytes site/static/images/password_strength.png | Bin 0 -> 93029 bytes site/static/{template => }/images/tile-01.jpg | Bin site/static/{template => }/images/tile-02.jpg | Bin site/static/js/default.js | 18 + site/static/js/jquery-2.1.4.min.js | 4 + site/templates/__init__.py | 0 site/templates/emails/__init__.py | 0 site/templates/emails/reset_password.md | 1 + site/tools/__init__.py | 0 site/tools/get_blog_images.py | 23 + site/tools/images/blog_image_0.jpg | Bin 0 -> 49949 bytes site/tools/images/blog_image_0.png | Bin 0 -> 90496 bytes site/tools/images/blog_image_1.jpg | Bin 0 -> 17713 bytes site/tools/images/blog_image_1.png | Bin 0 -> 22858 bytes site/tools/images/blog_image_10.jpg | Bin 0 -> 2861 bytes site/tools/images/blog_image_11.jpeg | Bin 0 -> 21726 bytes site/tools/images/blog_image_11.jpg | Bin 0 -> 21726 bytes site/tools/images/blog_image_12.jpg | Bin 0 -> 7530 bytes site/tools/images/blog_image_12.png | Bin 0 -> 3656 bytes site/tools/images/blog_image_13.jpg | Bin 0 -> 39565 bytes site/tools/images/blog_image_13.png | Bin 0 -> 42765 bytes site/tools/images/blog_image_14.jpg | Bin 0 -> 10521 bytes site/tools/images/blog_image_14.png | Bin 0 -> 9697 bytes site/tools/images/blog_image_15.jpg | Bin 0 -> 8315 bytes site/tools/images/blog_image_16.jpeg | Bin 0 -> 21184 bytes site/tools/images/blog_image_16.jpg | Bin 0 -> 21184 bytes site/tools/images/blog_image_17.jpg | Bin 0 -> 57381 bytes site/tools/images/blog_image_17.png | Bin 0 -> 79421 bytes site/tools/images/blog_image_18.jpg | Bin 0 -> 23601 bytes site/tools/images/blog_image_18.png | Bin 0 -> 28537 bytes site/tools/images/blog_image_19.jpg | Bin 0 -> 48986 bytes site/tools/images/blog_image_19.png | Bin 0 -> 43015 bytes site/tools/images/blog_image_2.jpg | Bin 0 -> 41457 bytes site/tools/images/blog_image_20.jpg | Bin 0 -> 25514 bytes site/tools/images/blog_image_20.png | Bin 0 -> 27705 bytes site/tools/images/blog_image_21.jpg | Bin 0 -> 41711 bytes site/tools/images/blog_image_22.jpg | Bin 0 -> 27577 bytes site/tools/images/blog_image_22.png | Bin 0 -> 28003 bytes site/tools/images/blog_image_23.jpg | Bin 0 -> 4142 bytes site/tools/images/blog_image_23.png | Bin 0 -> 5561 bytes site/tools/images/blog_image_24.jpg | Bin 0 -> 29038 bytes site/tools/images/blog_image_24.png | Bin 0 -> 36076 bytes site/tools/images/blog_image_25.jpg | Bin 0 -> 37410 bytes site/tools/images/blog_image_25.png | Bin 0 -> 45554 bytes site/tools/images/blog_image_26.jpg | Bin 0 -> 5323 bytes site/tools/images/blog_image_26.png | Bin 0 -> 9966 bytes site/tools/images/blog_image_27.jpg | Bin 0 -> 34444 bytes site/tools/images/blog_image_28.jpg | Bin 0 -> 23883 bytes site/tools/images/blog_image_28.png | Bin 0 -> 19261 bytes site/tools/images/blog_image_29.jpg | Bin 0 -> 11697 bytes site/tools/images/blog_image_29.png | Bin 0 -> 5598 bytes site/tools/images/blog_image_3.jpg | Bin 0 -> 7783 bytes site/tools/images/blog_image_30.jpg | Bin 0 -> 23601 bytes site/tools/images/blog_image_30.png | Bin 0 -> 28537 bytes site/tools/images/blog_image_4.jpg | Bin 0 -> 54878 bytes site/tools/images/blog_image_4.png | Bin 0 -> 36858 bytes site/tools/images/blog_image_5.jpg | Bin 0 -> 119568 bytes site/tools/images/blog_image_6.jpg | Bin 0 -> 10431 bytes site/tools/images/blog_image_7.jpg | Bin 0 -> 4577 bytes site/tools/images/blog_image_7.png | Bin 0 -> 10016 bytes site/tools/images/blog_image_8.jpg | Bin 0 -> 10713 bytes site/tools/images/blog_image_8.png | Bin 0 -> 7924 bytes site/tools/images/blog_image_9.jpg | Bin 0 -> 19052 bytes site/tools/images/blog_image_9.png | Bin 0 -> 11807 bytes site/widgets/action_bar.py | 24 + site/widgets/banner_slider.py | 9 +- site/widgets/change_password_box.py | 19 + site/widgets/chat.py | 2 +- site/widgets/columns.py | 6 +- site/widgets/email_template.py | 18 + site/widgets/example.py | 4 +- site/widgets/footer_content.py | 4 +- site/widgets/form.py | 31 + site/widgets/google_calendar.py | 34 + site/widgets/google_groups.py | 34 +- site/widgets/google_groups_signup.py | 6 +- site/widgets/google_hangout.py | 4 +- site/widgets/header_strip.py | 15 +- site/widgets/login_box.py | 43 + site/widgets/loginbox.py | 25 - site/widgets/member_card.py | 12 + site/widgets/member_tiles.py | 32 + site/widgets/messages.py | 14 + site/widgets/navigation_bar.py | 22 + site/widgets/page.py | 30 + site/widgets/password_box.py | 29 + site/widgets/popup.py | 21 + site/widgets/register_form.htm | 12 + site/widgets/register_form.py | 20 + site/widgets/twitter_feed.py | 4 +- site/wsgi.py | 5 + 154 files changed, 3353 insertions(+), 198 deletions(-) create mode 100644 site/authorize.py create mode 100644 site/config/settings.py create mode 100644 site/data/__init__.py create mode 100644 site/data/equipment.py create mode 100644 site/data/members.py create mode 100644 site/data/profile.py create mode 100644 site/data/site_user.py create mode 100644 site/data/sql/get_user_credentials.sql create mode 100644 site/data/sql/get_user_detail.sql create mode 100644 site/data/sql/get_user_password_reset.sql create mode 100644 site/data/sql/get_users.sql create mode 100644 site/data/sql/member_list.sql delete mode 100644 site/html/examples.html create mode 100644 site/libs/mail.py create mode 100644 site/libs/rss_invalid.xml create mode 100644 site/libs/rss_no_tags.xml create mode 100644 site/migrate.py create mode 100644 site/pages/calendar.py create mode 100644 site/pages/equipment.py create mode 100644 site/pages/examples.py create mode 100644 site/pages/google_groups.py create mode 100644 site/pages/members.py create mode 100644 site/pages/profile.py create mode 100644 site/sql/migrations/generated_column_indexes.sql create mode 100644 site/sql/migrations/generated_columns.sql create mode 100644 site/sql/migrations/generated_tables.sql rename site/static/{template => css}/default.css (68%) create mode 100644 site/static/css/sprite-action-white.css create mode 100644 site/static/css/sprite-content-white.css create mode 100644 site/static/css/sprite-navigation-white.css rename site/static/{template => }/images/background.jpg (100%) rename site/static/{template => }/images/background.png (100%) create mode 100644 site/static/images/banners/audio_board.jpg create mode 100644 site/static/images/banners/hackspace-banner.png create mode 100644 site/static/images/banners/microscope.jpg create mode 100644 site/static/images/banners/object_avoiding_robot.jpg create mode 100644 site/static/images/banners/rocket_camera.jpg create mode 100644 site/static/images/css/sprite-action-white.png create mode 100644 site/static/images/css/sprite-content-white.png create mode 100644 site/static/images/css/sprite-navigation-white.png rename site/static/{template => }/images/example-01.jpg (100%) rename site/static/{template => }/images/example-02.jpg (100%) rename site/static/{template => }/images/example-03.jpg (100%) create mode 100644 site/static/images/hackspace-banner.png rename site/static/{template => }/images/hackspace-banner.svg (100%) rename site/static/{template => }/images/hackspace.png (100%) rename site/static/{template => }/images/hackspace.svg (100%) create mode 100644 site/static/images/hackspace_960x540.png create mode 100644 site/static/images/hackspace_960x960.png rename site/static/{template => }/images/icon.png (100%) create mode 100644 site/static/images/oauth/google.png create mode 100644 site/static/images/password_strength.png rename site/static/{template => }/images/tile-01.jpg (100%) rename site/static/{template => }/images/tile-02.jpg (100%) create mode 100644 site/static/js/default.js create mode 100644 site/static/js/jquery-2.1.4.min.js create mode 100644 site/templates/__init__.py create mode 100644 site/templates/emails/__init__.py create mode 100644 site/templates/emails/reset_password.md create mode 100644 site/tools/__init__.py create mode 100644 site/tools/get_blog_images.py create mode 100644 site/tools/images/blog_image_0.jpg create mode 100644 site/tools/images/blog_image_0.png create mode 100644 site/tools/images/blog_image_1.jpg create mode 100644 site/tools/images/blog_image_1.png create mode 100644 site/tools/images/blog_image_10.jpg create mode 100644 site/tools/images/blog_image_11.jpeg create mode 100644 site/tools/images/blog_image_11.jpg create mode 100644 site/tools/images/blog_image_12.jpg create mode 100644 site/tools/images/blog_image_12.png create mode 100644 site/tools/images/blog_image_13.jpg create mode 100644 site/tools/images/blog_image_13.png create mode 100644 site/tools/images/blog_image_14.jpg create mode 100644 site/tools/images/blog_image_14.png create mode 100644 site/tools/images/blog_image_15.jpg create mode 100644 site/tools/images/blog_image_16.jpeg create mode 100644 site/tools/images/blog_image_16.jpg create mode 100644 site/tools/images/blog_image_17.jpg create mode 100644 site/tools/images/blog_image_17.png create mode 100644 site/tools/images/blog_image_18.jpg create mode 100644 site/tools/images/blog_image_18.png create mode 100644 site/tools/images/blog_image_19.jpg create mode 100644 site/tools/images/blog_image_19.png create mode 100644 site/tools/images/blog_image_2.jpg create mode 100644 site/tools/images/blog_image_20.jpg create mode 100644 site/tools/images/blog_image_20.png create mode 100644 site/tools/images/blog_image_21.jpg create mode 100644 site/tools/images/blog_image_22.jpg create mode 100644 site/tools/images/blog_image_22.png create mode 100644 site/tools/images/blog_image_23.jpg create mode 100644 site/tools/images/blog_image_23.png create mode 100644 site/tools/images/blog_image_24.jpg create mode 100644 site/tools/images/blog_image_24.png create mode 100644 site/tools/images/blog_image_25.jpg create mode 100644 site/tools/images/blog_image_25.png create mode 100644 site/tools/images/blog_image_26.jpg create mode 100644 site/tools/images/blog_image_26.png create mode 100644 site/tools/images/blog_image_27.jpg create mode 100644 site/tools/images/blog_image_28.jpg create mode 100644 site/tools/images/blog_image_28.png create mode 100644 site/tools/images/blog_image_29.jpg create mode 100644 site/tools/images/blog_image_29.png create mode 100644 site/tools/images/blog_image_3.jpg create mode 100644 site/tools/images/blog_image_30.jpg create mode 100644 site/tools/images/blog_image_30.png create mode 100644 site/tools/images/blog_image_4.jpg create mode 100644 site/tools/images/blog_image_4.png create mode 100644 site/tools/images/blog_image_5.jpg create mode 100644 site/tools/images/blog_image_6.jpg create mode 100644 site/tools/images/blog_image_7.jpg create mode 100644 site/tools/images/blog_image_7.png create mode 100644 site/tools/images/blog_image_8.jpg create mode 100644 site/tools/images/blog_image_8.png create mode 100644 site/tools/images/blog_image_9.jpg create mode 100644 site/tools/images/blog_image_9.png create mode 100644 site/widgets/action_bar.py create mode 100644 site/widgets/change_password_box.py create mode 100644 site/widgets/email_template.py create mode 100644 site/widgets/form.py create mode 100644 site/widgets/google_calendar.py create mode 100644 site/widgets/login_box.py delete mode 100644 site/widgets/loginbox.py create mode 100644 site/widgets/member_card.py create mode 100644 site/widgets/member_tiles.py create mode 100644 site/widgets/messages.py create mode 100644 site/widgets/navigation_bar.py create mode 100644 site/widgets/page.py create mode 100644 site/widgets/password_box.py create mode 100644 site/widgets/popup.py create mode 100644 site/widgets/register_form.htm create mode 100644 site/widgets/register_form.py create mode 100644 site/wsgi.py diff --git a/Dockerfile b/Dockerfile index 03bfb92..24ea3d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN \ apt-get update && \ apt-get upgrade -y && \ apt-get install -y software-properties-common python-software-properties && \ - apt-get install -y python-requests python-lxml python-flask python-tz + apt-get install -y python-requests python-lxml python-flask python-flask-login python-misaka python-tz python-mysqldb python-psycopg2 python-requests-oauthlib RUN add-apt-repository -y ppa:oly/ppa RUN apt-get update diff --git a/README.md b/README.md index 6c324f0..edcacf0 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,13 @@ Most of the content is generated statically to avoid hitting a database on each To generate static content you can run the code below. ``` python generate.py ``` + + + + +Run locally with uwsgi on port 9090 + +uwsgi --plugins python --http-socket :9090 -w wsgi + +Run locally with flask +python index.py diff --git a/requirements.txt b/requirements.txt index b74ecee..846d0a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ lxml requests pytz pip install -e bzr+lp:scaffold#egg=scaffold +python-requests-oauthlib diff --git a/site/authorize.py b/site/authorize.py new file mode 100644 index 0000000..86f385a --- /dev/null +++ b/site/authorize.py @@ -0,0 +1,371 @@ +import os +import sys +import time +import uuid +import hashlib +import datetime + +from werkzeug import secure_filename +from werkzeug.security import generate_password_hash, check_password_hash +from flask import Flask, session, flash, get_flashed_messages +from flask import redirect, abort +from flask import make_response +from flask import request +from flask import Blueprint +from flask.ext.login import LoginManager, login_required, UserMixin, login_user, logout_user, current_user, make_secure_token +from requests_oauthlib import OAuth2Session + + +from scaffold import web +from libs.mail import sendmail +from pages import header, footer +from pages import profile +from data import site_user +from config.settings import * +from constants import * + +web.load_widgets('widgets') + +authorize_pages = Blueprint('authorize_pages', __name__, template_folder='templates') + +login_manager = LoginManager() +login_manager.login_view = '/login' + + +def is_weak_password(password1, password2): + if password1 != password2: + password1 = password2 = None + return True + + # TODO check length and chars + + password1 = password2 = None + return False + + +def todict(data): + new_dict = {} + for key, value in data.items(): + new_dict[key] = value + return new_dict + + +class User(UserMixin): + def __init__(self, user_id, active=True): + print 'logged in ###########' + print user_id + user_details = site_user.get_user_details({'id': user_id}).get() + self.active = False + print user_details + if user_details: + #~ self.check_password(user_details.get('password')) + self.id = user_id + self.name = user_details.get('username') + self.team_id = user_details.get('team_id', 1) + self.active = active + + def get_id(self): + return self.id + + def is_active(self): + return self.active + + def is_authenticated(self): + return self.active + + def get_auth_token(self): + return make_secure_token(self.name, key='deterministic') + + +@login_manager.user_loader +def load_user(userid): + return User(userid) + + +@login_manager.token_loader +def load_token(request): + token = request.headers.get('Authorization') + if token is None: + token = request.args.get('token') + + if token is not None: + username, password = token.split(":") # naive token + print username + print password + user_entry = User.get(username) + if (user_entry is not None): + user = User(user_entry[0], user_entry[1]) + + if (user.password == password): + return user + return None + +def auth_required(): + if not session.get('user_id'): + redirect(domain + '/login', 301) + +@authorize_pages.route("/register", methods=['GET']) +def register_form(): + header('Register for access') + web.page.create('Register for access') + web.page.section(web.register_form.create().render()) + web.template.body.append(web.page.render()) + return make_response(footer()) + +@authorize_pages.route("/register", methods=['POST']) +def register_submit(): + data = {} + data['email'] = request.form.get('email') + data['username'] = request.form.get('email') + data['first_name'] = request.form.get('name').strip().split()[0] + data['last_name'] = request.form.get('name').strip().split()[-1] + + data['password'] = request.form.get('password') + data['password_confirm'] = request.form.get('password') + + + data['password'] = generate_password_hash(request.form.get('password')) + #TODO password strength tests + if is_weak_password(request.form.get('password'), request.form.get('password_confirm')): + print 'password not strong enough' + redirect('/register') + + header('Your account has been registered') + web.page.create('Your account has been registered') + + new_user = site_user.create() + new_user.execute(data) + flash('Your account has now been created') + + web.template.body.append(web.page.render()) + return make_response(footer()) + +@authorize_pages.route("/oauth", methods=['GET']) +@authorize_pages.route("/oauth/", methods=['GET']) +@authorize_pages.route("/oauth/", methods=['GET']) +def oauth(provider=None): + oauth_verify = True + oauth_provider = oauth_conf.get('google') + oauth_access_type = '' + oauth_approval_prompt = '' + if oauth_live is False: + oauth_verify = False + oauth_access_type = 'offline' + oauth_approval_prompt = "force" + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' + + print session + if provider: + oauth_session = OAuth2Session( + oauth_provider.get('client_id'), + scope=oauth_provider.get('scope'), + redirect_uri=oauth_provider.get('redirect_uri')) + + # offline for refresh token + # force to always make user click authorize + #generate the google url we will use to authorize and redirect there + authorization_url, state = oauth_session.authorization_url( + oauth_provider.get('auth_uri'), + access_type=oauth_access_type, + approval_prompt=oauth_approval_prompt) + + # State is used to prevent CSRF, keep this for later, make sure oauth returns to the same url. + session['oauth_state'] = state + return redirect(authorization_url) + + print session + #allready authorised so lets handle the callback + oauth_session = OAuth2Session( + oauth_provider.get('client_id'), + state=session['oauth_state'], + redirect_uri=oauth_provider.get('redirect_uri')) + + token = oauth_session.fetch_token( + oauth_provider.get('token_uri'), + client_secret=oauth_provider.get('client_secret'), + authorization_response=request.url, + verify=oauth_verify) + + # Fetch a protected resource, i.e. user profile + r = oauth_session.get('https://www.googleapis.com/oauth2/v1/userinfo') + + oauth_user = r.json() + + #https://www.googleapis.com/auth/plus.login + #https://www.googleapis.com/auth/plus.me + + print oauth_user + user_details = site_user.get_by_email({ + 'email': oauth_user.get('email') + }).get() + + if not user_details: + flash('Your new profile has been created, and your now logged in') + site_user.create().execute({ + 'email': oauth_user.get('email'), + 'password': 'oauth', + 'profile_image': oauth_user.get('picture'), + 'username': oauth_user.get('email'), + 'first_name': oauth_user.get('given_name'), + 'last_name': oauth_user.get('family_name')}) + user_details = site_user.get_by_email({ + 'email': oauth_user.get('email') + }).get() + + user = User(user_details.get('user_id')) + login_user(user) + site_user.update_last_login().execute(user_details) + return redirect('/profile') + +@authorize_pages.route("/change-password/", methods=['GET']) +@authorize_pages.route("/change-password", methods=['GET']) +def change_password(code=None): + #if we have a code this is a password reset, so try and login the user first + print code + site_user.delete_password_reset().execute({}) + if code: + + user_details = site_user.get_user_by_reset_code({'reset_code': code}).get() + + print user_details + if not user_details: + #invalid code so pretend the page does not even exist + return abort(404) + #check the code has not expired + #datetime.datetime.now() + datetime.timedelta(minutes=15) + has_date_expired = user_details.get('created') + datetime.timedelta(minutes=60) + if has_date_expired < datetime.datetime.now(): + print 'date expired' + #date expired so clean up and pretend the page does not exist + return abort(404) + #challenge passed so login the user so they can change there password + login_user( + User(user_details.get('user_id')) + ) + session['user_id'] = str(user_details.get('user_id')) + + web.template.create('Maidstone Hackspace - Profile') + header('User profile') + web.page.create('Change password') + web.page.section( + web.change_password_box.create().render() + ) + web.template.body.append(web.page.render()) + return make_response(footer()) + +@login_required +@authorize_pages.route("/change-password", methods=['POST']) +def change_password_submit(code=None): + if not session.get('user_id'): + abort(404) + user_details = site_user.authorize({ + 'id': session.get('user_id')}).get() + + if is_weak_password(request.form.get('password'), request.form.get('password_confirm')): + print 'password not strong enough' + redirect('/login') + + pw_hash = generate_password_hash(request.form.get('password')) + + site_user.change_password().execute({'id': user_details.get('user_id'), 'password': pw_hash}) + + web.template.create('Maidstone Hackspace - Profile') + header('User Profile') + web.page.create('Password change complete') + web.page.section( + 'Your password has successfull been changed' + ) + web.template.body.append(web.page.render()) + return make_response(footer()) + + + +@authorize_pages.route("/reset-password", methods=['GET']) +def reset_password(): + web.template.create('Maidstone Hackspace - Login') + header('Members Login') + web.page.create('Forgot password reset') + web.page.section( + web.password_box.create('Please enter your E-Mail account', reset=True).render() + ) + web.template.body.append(web.page.render()) + return make_response(footer()) + +@authorize_pages.route("/reset-password", methods=['POST']) +def reset_password_submit(): + user_details = site_user.get_by_username({ + 'email': request.form.get('email')}).get() + + reset_code = hashlib.sha256(str(uuid.uuid4())).hexdigest() + + if user_details: + site_user.create_password_reset() \ + .on_duplicate() \ + .execute({ + 'user_id': str(user_details.get('user_id')), + 'reset_code': reset_code}) + + l=web.link.create(title='Change password', content='Click to change password',link="{domain}change-password/{resetcode}".format(**{'domain':app_domain, 'resetcode': reset_code})).render() + + body = "Please follow the link below to change your password.\n" + l + body += "{domain}change-password/{resetcode}".format(**{'domain':app_domain, 'resetcode': reset_code}) + sendmail().send(from_address='no-reply@maidstone-hackspace.org.uk', to_address='oly@leela', subject="Reset password request", body=body) + + # display success page, dont give away anything about if the email is actually registered + web.template.create('Maidstone Hackspace - Password reset') + header('Password reset') + web.page.create('Password reset sent') + web.page.section( + web.paragraph.create('If this E-Mail is registered you will shortly be reciving an E-Mail with reset details').render() + ) + web.template.body.append(web.page.render()) + return make_response(footer()) + + +@authorize_pages.route("/login", methods=['GET']) +def login_screen(): + web.template.create('Maidstone Hackspace - Login') + header('Members Login') + web.page.create('Member Login') + web.page.section( + web.login_box.create().enable_oauth('google').render() + ) + #~ web.template.body.append(web.messages.render()) + web.template.body.append(web.page.render()) + return make_response(footer()) + + +@authorize_pages.route("/login", methods=['POST']) +def login_screen_submit(): + """handle the login form submit""" + # try to find user by username + user_details = site_user.get_by_username({ + 'email': request.form.get('username')}).get() + + #not found so lets bail to the login screen + if not user_details: + flash('Failed to login with that username and password, please retry.') + return login_screen() + + #now lets verify the users password, and bail if its wrong + pw_hash = generate_password_hash(request.form.get('password')) + if check_password_hash(pw_hash, user_details.get('password')): + flash('Failed to login with that username and password, please retry.') + return login_screen() + + #login user and redirect to profile + login_user( + User(user_details.get('user_id')) + ) + flash('You have successfully logged in !') + #~ session['username'] = user_details.get('username', 'anonymous') + #~ session['user_id'] = str(user_details.get('user_id')) + site_user.update_last_login(user_details) + return redirect('/profile') + + +@authorize_pages.route("/logout") +@login_required +def logout(): + logout_user() + return redirect('/') diff --git a/site/config/settings.py b/site/config/settings.py new file mode 100644 index 0000000..699c949 --- /dev/null +++ b/site/config/settings.py @@ -0,0 +1,81 @@ +import os +from scaffold.core.data.database import db +from scaffold import web + +schema = 'https:' +domain = '127.0.0.1' +port = '5000' +rel_uri = '//127.0.0.1:5000' +app_domain = 'http:%s' % rel_uri +app_email_template_path = 'templates/email/' + +from_email = 'no-reply@maidstone-hackspace.org.uk' + +flask_secret_key = '4466ae96-849f-4fbe-a469-3295bf1a13f5' + +database = { + 'charset': 'utf8', + 'use_unicode': True, + 'type': 'mysql', + 'host': '127.0.0.1', + 'user': 'root', + 'passwd': "", + 'db': "maidstone_hackspace", + 'port': 3306} + + +oauth_live = False +oauth_redirect_uri = app_domain + '/oauth' +oauth_conf = { + 'google': {}, +} + + +gocardless_enviroment = 'sandbox' +gocardless_credentials = { + 'app_id': '', + 'app_secret': '', + 'access_token': '', + 'merchant_id': '' +} + + + +if os.path.exists('config/settings_dev.py'): + print 'Using settings for dev enviroment' + from settings_dev import * + +if os.path.exists('config/settings_testing.py'): + print('Using settings for test enviroment') + from settings_testing import * + +if os.path.exists('config/settings_live.py'): + print('Using settings for live enviroment') + from settings_live import * + + + +with web.template as setup: + #css + setup.persistent_header('') + setup.persistent_header('') + setup.persistent_header('') + setup.persistent_header('') + setup.persistent_header('') + + #javascript + setup.persistent_header('') + setup.persistent_header('') + setup.persistent_header('') + setup.persistent_header('') + + #other favicon etc + setup.persistent_header('') + + setup.persistant_uris( + schema=schema, + domain=domain, + port=port) + + +db.config(database) diff --git a/site/constants.py b/site/constants.py index 160d6d5..e413552 100644 --- a/site/constants.py +++ b/site/constants.py @@ -11,8 +11,8 @@ banner_images = [ ('/static/images/banners/microscope.jpg', '', 'Microscope', ''), ('/static/images/banners/object_avoiding_robot.jpg', '', 'Object avoiding robot', ''), ('/static/images/banners/rocket_camera.jpg', 'Rocket Camera', 'Rocket Camera', '')] - #~ ('/static/template/images/example-01.jpg', '', 'title', 'intro text'), - #~ ('/static/template/images/example-02.jpg', '', 'title', 'intro text')] + #~ ('/static/images/example-01.jpg', '', 'title', 'intro text'), + #~ ('/static/images/example-02.jpg', '', 'title', 'intro text')] tile_images = [ ('/static/images/tiles/malta-inn.jpg',), @@ -38,3 +38,10 @@ email_server = { 'port': 465, 'from': 'support@maidstone-hackspace.org.uk', 'to': 'support@maidstone-hackspace.org.uk'} + + +url_home= '/' +url_profile = '/profile' +url_change_password = '/change_password' +url_change_password = '/reset_password' +url_login = '/login' diff --git a/site/data/__init__.py b/site/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/site/data/equipment.py b/site/data/equipment.py new file mode 100644 index 0000000..bd66086 --- /dev/null +++ b/site/data/equipment.py @@ -0,0 +1,45 @@ +import os +import time +import sys +sys.path.append(os.path.abspath('../../../../scaffold/')) +sys.path.insert(0,os.path.abspath('../../../../scaffold/')) +from scaffold.core.data.select import select_data +from scaffold.core.data.insert import insert_data +from scaffold.core.data.update import update_data +from scaffold.core.data.delete import delete_data +from scaffold.core.data.sql import query_builder + +query_builder.query_path = os.path.abspath('./data/sql/') + +class create(insert_data): + table = 'requests' + required = {'user_id', 'name'} + columns = {'user_id', 'name'} + columns_optional = {'price', 'description', 'url'} + +class update(update_data): + #~ debug = True + table = 'requests' + required = {'id', 'user_id'} + columns = {'user_id', 'name'} + columns_where = {'id'} + columns_optional = {'price', 'description', 'url'} + +class get_requests(select_data): + debug = True + #~ limit_rows = False + pagination_rows = 100 + required = {} + #~ query_str = 'select id, user_id, name, description, url, price, count(user_id) as quantity from maidstone_hackspace.requests group by name' + query_str = 'select id, user_id, name, description, url, price as quantity from maidstone_hackspace.requests order by name' + columns = {} + + +class get_request(select_data): + table = 'requests' + required = {'id'} + columns = {'*'} + #query_str = 'select id, user_id, name, description, url, price, count(user_id) as quantity from maidstone_hackspace.requests group by name' + columns_where = {'id'} + #columns = {} + diff --git a/site/data/members.py b/site/data/members.py new file mode 100644 index 0000000..0e31d98 --- /dev/null +++ b/site/data/members.py @@ -0,0 +1,21 @@ +import os +import time +from scaffold.core.data.select import select_data +from scaffold.core.data.insert import insert_data +from scaffold.core.data.update import update_data +from scaffold.core.data.delete import delete_data +from scaffold.core.data.sql import query_builder + +query_builder.query_path = os.path.abspath('./data/sql/') + + +class get_members(select_data): + required = {} + query_file = 'member_list.sql' + columns = {} + + +class get_member_profile(select_data): + required = {'id'} + query_file = 'get_users.sql' + columns_where = {'id'} diff --git a/site/data/profile.py b/site/data/profile.py new file mode 100644 index 0000000..2fed4bc --- /dev/null +++ b/site/data/profile.py @@ -0,0 +1,19 @@ +import os +import time + +from scaffold.core.data.select import select_data +from scaffold.core.data.insert import insert_data +from scaffold.core.data.update import update_data +from scaffold.core.data.delete import delete_data +from scaffold.core.data.sql import query_builder + +query_builder.query_path = os.path.abspath('./data/sql/') + + + +class update_description(update_data): + #~ debug = True + table = 'user_detail' + required = {'user_id', 'description', 'skills'} + columns = {'user_id', 'description', 'skills'} + diff --git a/site/data/site_user.py b/site/data/site_user.py new file mode 100644 index 0000000..5fab090 --- /dev/null +++ b/site/data/site_user.py @@ -0,0 +1,81 @@ +import os +import time +from scaffold.core.data.select import select_data +from scaffold.core.data.insert import insert_data +from scaffold.core.data.update import update_data +from scaffold.core.data.delete import delete_data +from scaffold.core.data.sql import query_builder + +query_builder.query_path = os.path.abspath('./data/sql/') + + +class create(insert_data): + table = 'users' + required = {'email', 'password', 'username', 'first_name', 'last_name', 'created'} + columns = {'email', 'password', 'username', 'first_name', 'last_name', 'created'} + columns_optional = {'profile_image'} + + def calculated_data(self): + return {'created': time.strftime('%Y-%m-%d %H:%M:%S')} + + def set(self, data): + data['created'] = time.strftime('%Y-%m-%d %H:%M:%S') + super(create, self).set(data) + +class update_last_login(update_data): + #~ table = 'users' + debug = True + query_str = "update `users` set `last_login`=now() where id=%(user_id)s" + required = {'user_id'} + #~ columns = {'id'} + columns_where = {} + +class delete_password_reset(delete_data): + """clean up expired password resets""" + table = 'user_password_reset' + sql_where = 'where DATE_ADD(created, INTERVAL 1 HOUR) < now()' + required = {} + + +class create_password_reset(insert_data): + table = 'user_password_reset' + required = {'user_id', 'reset_code'} + +class get_user_by_reset_code(select_data): + required = {'reset_code'} + query_file = 'get_user_password_reset.sql' + columns_where = ['reset_code'] + +class change_password(update_data): + table = 'users' + required = {'id', 'password'} + columns_where = ['password'] + sql_where = 'id=%(id)s' + + +class get_users(select_data): + required = {} + query_file = 'get_users.sql' + + +class get_user_details(select_data): + #~ debug = True + required = {'id'} + query_file = 'get_user_detail.sql' + columns_where = {'users.id'} + + +class get_by_email(select_data): + required = {'email'} + query_file = 'get_users.sql' + columns_where = {'email'} + +class get_by_username(select_data): + required = {'email'} + query_file = 'get_user_credentials.sql' + columns_where = {'email'} + +class authorize(select_data): + required = {'id'} + query_file = 'get_user_credentials.sql' + columns_where = {'id'} diff --git a/site/data/sql/get_user_credentials.sql b/site/data/sql/get_user_credentials.sql new file mode 100644 index 0000000..9428b27 --- /dev/null +++ b/site/data/sql/get_user_credentials.sql @@ -0,0 +1 @@ +select id as user_id, username, email, password from users diff --git a/site/data/sql/get_user_detail.sql b/site/data/sql/get_user_detail.sql new file mode 100644 index 0000000..b2782c3 --- /dev/null +++ b/site/data/sql/get_user_detail.sql @@ -0,0 +1,4 @@ +select users.id as user_id, members.id as member_id, username, first_name, last_name, email, users.profile_image, last_login, description, skills + from users + left join members on users.id=members.user_id + left join user_detail on users.id = user_detail.user_id diff --git a/site/data/sql/get_user_password_reset.sql b/site/data/sql/get_user_password_reset.sql new file mode 100644 index 0000000..b0b8d44 --- /dev/null +++ b/site/data/sql/get_user_password_reset.sql @@ -0,0 +1 @@ +select id as user_id, reset_code, created from user_password_reset diff --git a/site/data/sql/get_users.sql b/site/data/sql/get_users.sql new file mode 100644 index 0000000..4569b04 --- /dev/null +++ b/site/data/sql/get_users.sql @@ -0,0 +1 @@ +select id as user_id, username, first_name, email, profile_image, last_login from users diff --git a/site/data/sql/member_list.sql b/site/data/sql/member_list.sql new file mode 100644 index 0000000..ad35e17 --- /dev/null +++ b/site/data/sql/member_list.sql @@ -0,0 +1,3 @@ +select users.id as user_id, first_name, last_name, users.profile_image, description, skills + from users + left join user_detail on user_detail.user_id=users.id diff --git a/site/generate.py b/site/generate.py index 13a3004..1731b2d 100644 --- a/site/generate.py +++ b/site/generate.py @@ -6,8 +6,7 @@ import argparse sys.path.append(os.path.abspath('../../../scaffold/')) sys.path.insert(0,os.path.abspath('../../../scaffold/')) -from scaffold.web import web as html -from scaffold.web import www +from scaffold import www from libs.rss_fetcher import feed_reader diff --git a/site/html/blog.html b/site/html/blog.html index e69de29..1cb8585 100644 --- a/site/html/blog.html +++ b/site/html/blog.html @@ -0,0 +1,118 @@ + + + + + + + + + + + + + +
+ +
+ +
+
test1
test2
+
+

Backlinks and SEO By Mathew Beddow

Following a recent discussion I had with a university dive club member about a request to remove a back-link from a now dead forum from a travel insurance company, I thought I’d take this opportunity to delve into the mystical … Continue reading

A better Arduino IDE By Mike McRoberts

As anyone who has used the Arduino IDE over time will tell you, this crappy piece of software has barely changed since the Arduino first surfaced. It has no autocompletion, […]

GTK3 Simple Opengl application with gtk and Touch screen events By Oliver Marks

Example application mixing gtk, opengl and touch, written as a demo but also as an opengl testing enviroment.

Torrent File Parser By Simon Ridley

This is a great script for obtaining the metainfo contained within torrent files. Due to the encoding used, keyword searches conducted during computer forensic analysis may not return any results when analyzing these types of files. This ruby script decodes the torrent, allowing an examiner to view the contained information, such as tracker list, file names, file sizes, directories, MD5 sum, etc. Credit goes to Rob Williams for creating this, all I did was alter the Bencoding Library, as I was unable to get the original to function. Hopefully you'll find this as useful as I did!

When Microsoft calls a Vulnerability a “Feature” By Mathew Beddow

Also known as “When responsible disclosure gets you no-where, make them listen by going public” but it didn’t have such a good ring to it. So, to the meat of the business. I have a Nokia Lumia 920 which a … Continue reading

ATMega328P Power Saving Techniques By Mike McRoberts

I was sent a link to to the following great article on power saving techniques for microprocessors and in particular the ATMEga328P which is the chip used in the Arduino. […]

Draw a simple triangle with Kivy By Oliver Marks

Kivy example on setting up and displaying a basic triangle

Twitter Profile Extraction Tool API Update By Simon Ridley

After a couple of requests from members on Forensic Focus, I've updated this script so that profiles can be accessed / extracted via Twitter's API update 1.1. I had converted the original script into an executable which was available for download on SourceForge, however since the API now requires authentication, I don't like the idea of packaging my own API keys into this. All this means is that you will require your own API keys, which you can create at Twitter's Application management page.

I’ll try not to drone on… By Mathew Beddow

It would seem that everyone and their dog are getting their own Quadcopter or quadrotor (often misreported in the media as a drone, but that’s another story). A recent build day ran by Reading Hackspace allowed me to jump into their … Continue reading

Using an ESP8266 as a time source (Part 1) By Mike McRoberts

So i’ve obtained some ESP8266 WiFi modules lately and have been having a play with them. If you’ve not heard of the ESP8266 they are tiny serial controlled WiFi modules […]

Draw a textured square with Kivy By Oliver Marks

Kivy example drawing a square and loading an image and applying to the quad as a simple texture.

Keyword Tracking Live Tweets By Simon Ridley

A little experiment script I cobbled together for live incident tracking over Twitter, very useful for identifying, and evidencing signs of racism or death threats towards others. If the scenario requires it, geo data can be included in the capture, along with tweet source which identifies how the tweet was made either via a mobile device or a web browser. Also quite handy if and when exhibits need to be seized, and you need to narrow down what device the tweets were made from.

Recovering Deleted Tweets By Simon Ridley

Occasionally you may find that a notable Twitter profile may remove tweets, or be shut down before preservation can be initiated by the forensic examiner. Should this be the case, you may be required to refer to caching services such as Google Cache or Twicsy for example. I recently had need to evidence data found on Twicsy, however the web interface isn't exactly forensic friendly when it comes to how it displays the data. Twicsy.com is a Twitter picture search engine, which appears to duplicate the original textual data from a tweet containing an image, and stores this information on their own web server. The image from a tweet is simply referred to from the source, and if the tweet is deleted or the profile removed, you'll find the image won't exist any more. However the textual data does still remains on Twicsy's website despite the original tweet not existing. After discovering this I wrote a ruby script to extract each of the archived tweets and place it into a format which is readable.

Using an ESP8266 as a time source (Part 2) By Mike McRoberts

Since my last attempt at using an ESP8266 to get the date and time from the internet I’ve tried out another module, this time with the NodeMCU firmware. This is […]

Draw a cube mixing kivy widgets By Oliver Marks

Kivy example on setting up a display and drawing a basic triangle

DIY 3D Delta Printer for Ceramic - Introduction By Simon Ridley

In October last year, I got the opportunity to build a RepRap Huxley pro 3D printer for a community crafts centre based in Ashford, Kent. The craft centre wanted a means to demonstrate to its hub of artists how to incorporate new technologies such as 3D printing into their studio work, to create never before seen creations. By building the Huxley it provided me the confidence to build my own 3D printer, which in a series of posts I'm going to discuss further.

8MHz Node Test By Mike McRoberts

I’ve resurrected the Sensor Node project and I am experimenting in reducing the power consumption even further. This time I’ve removed the 3.3v voltage regulator from the circuit and I’ve […]

Review of OpenGL ES 3.0 programming guide By Oliver Marks

Short review of "OpenGL ES 3.0 programming guide"

Arduino Video Tutorials By Mike McRoberts

Sorry for it being such a long time since my last post. I have been so busy with other non-Arduino related things that I’ve barely had time to update the […]

Draw two cubes using Kivy with different shaders. By Oliver Marks

Kivy example draw two cubes with different shaders and vertices so they can be moved seperately.

The Arduino Acadamy – Lesson 1 – An Introduction to the Arduino By Mike McRoberts

So, here is my first video for The Arduino Academy. This lesson will introduce you to the #Arduino, tell you what an Arduino is and what you can use it […]

GTK3 custom signals example By Oliver Marks

Simple example on how to setup, connect to and trigger signals.

The Arduino Academy – Lesson 2 – Basic Outputs By Mike McRoberts

So I present to you Lesson 2 from The Arduino Academy – Basic Outputs: Look out for further lessons: Lesson 3 – Basic Inputs (Digital) Lesson 4 – Basic Inputs […]

Rendering textured pixels with OpenGL Example By Oliver Marks

OpenGL program that does pixel shading, OpenGL pixels with size and textures often used for particle effects.

Retro Ramblings By Mike McRoberts

Crikey, nearly 3 weeks since my last post. Apologies if you’ve been waiting on part 3 of The Arduino Academy, but I’ve got slightly sidetracked lately with several things. Firstly, […]

Review of "From Mathematics to Generic Programming" By Oliver Marks

Short review of "From Mathematics to Generic Programming"

FLiR Lepton Thermal Camera Module By Mike McRoberts

My FLiR Lepton Thermal Camera Module has finally arrived from the USA. I managed to hook it up to my Raspberry Pi yesterday and successfully receive thermal images from it. The […]

Create your project repository By Oliver Marks

Howto create a new project and push it to launchpad.

Create a new login greeter entry By Oliver Marks

Populate a listbox with custom widgets, in this case an example file downloader.

Generating a debian package By Oliver Marks

Using launchpad we will create a ppa which will automatically generate your package from the standard debian packaging files.

Retrieving window details By Oliver Marks

Howto get a list of open windows and there titles.

Using properties and atoms By Oliver Marks

This example shows how to get properties and use atoms.

Debugging with XCB By Oliver Marks

Debugging XCB and catching errors and exploring the library.

Querying Extensions By Oliver Marks

Query available extensions and checking for availability.

Review of "App Accomplished" By Oliver Marks

Short review of "App Accomplished"

Querying display information By Oliver Marks

Example on detecting screen sizes and number of screens in a multi head setup.

Creating a simple window By Oliver Marks

Example on creating new window and attaching them to the root window.

Handling X mouse events By Oliver Marks

Example demonstrating handling X mouse events.

Handling X keyboard events By Oliver Marks

Example demonstrating handling X keybaord events.

X Desktop Tutorial. By Oliver Marks

Using X build a desktop, learn to package and deploy it and create a login screen.

Review of "Effective Python" By Oliver Marks

Short review of "Effective Python"

XCB utility method for examing available methods By Oliver Marks

Simple extended dir function for inspecting xcb.

XCB loading and displaying images By Oliver Marks

Helper class which will load in a png and convert it ready for display in a X window.

Class for generating a grid to position and size windows By Oliver Marks

This is a helper class to split an area up into increasingly smaller areas.

+
+
+
+ +