PKMOi l''flask_simplelogin/__init__.py"""Flask Simple Login - Login Extension for Flask""" __version__ = '0.0.7' __author__ = 'Bruno Rocha' __email__ = 'rochacbruno@gmail.com' import logging import os from functools import wraps from uuid import uuid4 from warnings import warn from flask import (Blueprint, current_app, flash, redirect, render_template, request, session, url_for) from flask_wtf import FlaskForm from wtforms import PasswordField, StringField from wtforms.validators import DataRequired logger = logging.getLogger(__name__) class LoginForm(FlaskForm): "Default login form" username = StringField('name', validators=[DataRequired()]) password = PasswordField('password', validators=[DataRequired()]) def default_login_checker(user): """user must be a dictionary here default is checking username/password if login is ok returns True else False :param user: dict {'username':'', 'password': ''} """ username = user.get('username') password = user.get('password') the_username = os.environ.get( 'SIMPLELOGIN_USERNAME', current_app.config.get('SIMPLELOGIN_USERNAME', 'admin') ) the_password = os.environ.get( 'SIMPLELOGIN_PASSWORD', current_app.config.get('SIMPLELOGIN_PASSWORD', 'secret') ) if username == the_username and password == the_password: return True return False def is_logged_in(username=None): """Checks if user is logged in if `username` is passed check if specified user is logged in username can be a list""" if username: if not isinstance(username, (list, tuple)): username = [username] return 'simple_logged_in' in session and get_username() in username return 'simple_logged_in' in session def get_username(): """Get current logged in username""" return session.get('simple_username') def login_required(function=None, username=None, basic=False, must=None): """Decorate views to require login @login_required @login_required() @login_required(username='admin') @login_required(username=['admin', 'jon']) @login_required(basic=True) @login_required(must=[function, another_function]) """ if function and not callable(function): raise ValueError( 'Decorator receives only named arguments, ' 'try login_required(username="foo")' ) def check(validators): """Return in the first validation error, else return None""" if validators is None: return if not isinstance(validators, (list, tuple)): validators = [validators] for validator in validators: error = validator(get_username()) if error is not None: return SimpleLogin.get_message('auth_error', error), 403 def dispatch(fun, *args, **kwargs): if basic and request.is_json: return dispatch_basic_auth(fun, *args, **kwargs) if is_logged_in(username=username): return check(must) or fun(*args, **kwargs) elif is_logged_in(): return SimpleLogin.get_message('access_denied'), 403 else: flash(SimpleLogin.get_message('login_required'), 'warning') return redirect(url_for('simplelogin.login', next=request.path)) def dispatch_basic_auth(fun, *args, **kwargs): simplelogin = current_app.extensions['simplelogin'] auth_response = simplelogin.basic_auth() if auth_response is True: return check(must) or fun(*args, **kwargs) else: return auth_response if function: @wraps(function) def simple_decorator(*args, **kwargs): """This is for when decorator is @login_required""" return dispatch(function, *args, **kwargs) return simple_decorator def decorator(f): """This is for when decorator is @login_required(...)""" @wraps(f) def wrap(*args, **kwargs): return dispatch(f, *args, **kwargs) return wrap return decorator class SimpleLogin(object): """Simple Flask Login""" messages = { 'login_success': 'login success!', 'login_failure': 'invalid credentials', 'is_logged_in': 'already logged in', 'logout': 'Logged out!', 'login_required': 'You need to login first', 'access_denied': 'Access Denied', 'auth_error': 'Authentication Error: {0}' } @staticmethod def get_message(message, *args, **kwargs): """Helper to get internal messages outside this instance""" msg = current_app.extensions['simplelogin'].messages.get(message) if msg and (args or kwargs): return msg.format(*args, **kwargs) return msg def __init__(self, app=None, login_checker=None, login_form=None, messages=None): self.config = { 'blueprint': 'simplelogin', 'login_url': '/login/', 'logout_url': '/logout/', 'home_url': '/' } self.app = None self._login_checker = login_checker or default_login_checker self._login_form = login_form or LoginForm if app is not None: self.init_app( app=app, login_checker=login_checker, login_form=login_form, messages=messages ) def login_checker(self, f): """To set login_checker as decorator: @simple.login_checher def foo(user): ... """ self._login_checker = f return f def init_app(self, app, login_checker=None, login_form=None, messages=None): if login_checker: self._login_checker = login_checker if login_form: self._login_form = login_form if messages and isinstance(messages, dict): self.messages.update(messages) self._register(app) self._load_config() self._set_default_secret() self._register_views() self._register_extras() def _register(self, app): if not hasattr(app, 'extensions'): app.extensions = {} if 'simplelogin' in app.extensions: raise RuntimeError("Flask extension already initialized") app.extensions['simplelogin'] = self self.app = app def _load_config(self): config = self.app.config.get_namespace( namespace='SIMPLELOGIN_', lowercase=True, trim_namespace=True ) # backwards compatibility old_config = self.app.config.get_namespace( namespace='SIMPLE_LOGIN_', lowercase=True, trim_namespace=True ) config.update(old_config) if old_config: msg = ( "Settings defined as SIMPLE_LOGIN_ will be deprecated. " "Please, use SIMPLELOGIN_ instead." ) warn(msg, FutureWarning) self.config.update(old_config) self.config.update( dict((key, value) for key, value in config.items() if value) ) def _set_default_secret(self): if self.app.config.get('SECRET_KEY') is None: secret_key = str(uuid4()) logger.warning(( 'Using random SECRET_KEY: {0}, ' 'please set it on your app.config["SECRET_KEY"]' ).format(secret_key)) self.app.config['SECRET_KEY'] = secret_key def _register_views(self): self.blueprint = Blueprint( self.config['blueprint'], __name__, template_folder='templates' ) self.blueprint.add_url_rule( self.config['login_url'], endpoint='login', view_func=self.login, methods=['GET', 'POST'] ) self.blueprint.add_url_rule( self.config['logout_url'], endpoint='logout', view_func=self.logout, methods=['GET'] ) self.app.register_blueprint(self.blueprint) def _register_extras(self): self.app.add_template_global(is_logged_in) self.app.add_template_global(get_username) def basic_auth(self, response=None): """Support basic_auth via /login or login_required(basic=True)""" auth = request.authorization if auth and self._login_checker({'username': auth.username, 'password': auth.password}): session['simple_logged_in'] = True session['simple_basic_auth'] = True session['simple_username'] = auth.username return response or True else: headers = {'WWW-Authenticate': 'Basic realm="Login Required"'} return 'Invalid credentials', 401, headers def login(self): destiny = request.args.get( 'next', default=request.form.get( 'next', default=self.config.get('home_url', '/') ) ) if is_logged_in(): flash(self.messages['is_logged_in'], 'primary') return redirect(destiny) if request.is_json: # recommended to use `login_required(basic=True)` instead this return self.basic_auth(destiny=redirect(destiny)) form = self._login_form() ret_code = 200 if form.validate_on_submit(): if self._login_checker(form.data): flash(self.messages['login_success'], 'success') session['simple_logged_in'] = True session['simple_username'] = form.data.get('username') return redirect(destiny) else: flash(self.messages['login_failure'], 'danger') ret_code = 401 # <-- invalid credentials RFC7235 return render_template('login.html', form=form, next=destiny), ret_code def logout(self): session.clear() flash(self.messages['logout'], 'primary') return redirect(self.config.get('home_url', '/')) PKMO>;<<&flask_simplelogin/templates/login.html Flask Login {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% else %} {% endif %} {% endwith %} {%if form.errors %} {% endif %}
{{ form.csrf_token }} {{form.username.label}}
{{ form.username }}

{{form.password.label}}
{{ form.password }}

{% if next %}{% endif %} PKMO,I,,)flask_simplelogin-0.0.7.dist-info/LICENSEMIT License Copyright (c) 2017 Bruno Rocha Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HMuSa'flask_simplelogin-0.0.7.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UD"PK!H/*flask_simplelogin-0.0.7.dist-info/METADATAZ[oɱ~_ѦKDzq^%b9i 3=#+kv$p@_)!_U\xh5]U]]]uUmVq;7=1 \}>*2*r , dωR0#RѶ}ʈ$N "e{N,MNU:/^g^)Wv'M(DZ7sX[ z"1oLLip]ﮟgf\.*TߑK<Ŷ;Lۃ]څQ2ra ZMs CMŞM3_0CJ=?Σ%/}JF zS^v'Ób{>O A>89'"sAӧ:]oRT'N7i.G7j2_U18綾 7՘/rZN覟P3ݨ4. Ts7dj t`g0hSq.D L?XHKaqƀQēLh+D}7:+ |MLsr[<}ܚ>oEϗϗWWϣ[ѭ5mX-ɖd+ `rzZj=u @$2MC3ʞZ-bxT*, b4NqD/JBR'O]茿,KJ%$ϳȣ쒠؛I\ȴLS!Nj75: %/} e:C;N#do]6e̾ ȀuTQ\7;(.B}?SGH֐"t#2;CKX#cq; ̹ȴߝ,q 1ۦpƞ'Fq:版'M鹑U|cb`H;bKM;=\byڿ`_ėdfa*"XO8+?tH\ akD#FsS!:LB[^x51hѝE߰5Cdr8l:(&oj9 AF0kTd?p ,@64;&㤕/'n||a. ΁8DHXHwơY7(-#ZJ1;($f8wL@p&4̣)aHv&j%lrY`V?}v;jM-HH!/v`d`{y;hxGq(]wn r [ g鐁$Mt 9!;c Y4}хPD*z%DEqEœ9>\1Qn60%𤌼0NL;;V⽧Nu \6O}Jӵgh'$8 C!+72͞#VnlzAO⸾%9M`eLe(_BISH246>MdI$I&+ms7%ׂrK,]@'Β(;ū=Rp-]_ۤdz8'ȉzQ5G[̌\P oR h{4UQW |ҏп8߭Rb7<-S/olDM7Mw)d6`En:a?ŷTFg-GN@wյ|u U'8;ň2<'/xiT >W/6$64,E,}4F8++9o@b`Y :m rN `*Eve %1 O7䌜5]ܬ r7Y@ˈFg+nmntٗb(0_'Z`n<̑ WHLDY }bܑ(. Bɠv]1S}"\d nlW+-VK?AkC"I{_ݿf7Pϥ cDy8{~4s5&]~u욕 0E9z%Gㄋ|rl$JM zB [_*:J5ϖ?p' !50\-[K1tSsY\Ishh(BD*eVg%EaPՈxaځq2}|X{VfYpQ@_JNS 5')+rINWЙav hع[4\Z2q]ܻw.R5mDfJ6AJgs6? 7X:2Ii @m57 u m Ƥ$P~KX* uYfzrul׹'B+;x}mGdw^QwSLa UpI`$B-=C5 _SIvdMKÔA*A"8!0_j< +ҩ+g0ɒ2EJ^p a >W$}lzHLdhP(ܐޥ rD`g%J#㾅pOG]m ٠ܬ71 f7:.}oSM=6ŝW #%w.G@8Է6 Ҥ̣94QWu b{:*,ƙ·?"WrnK&J|̰̉hcڍ~\gEDO_zg%7a?c5[*N/(^11B|O3~w ZѨL_ǒѾXʚ#`eD:[-\Ñ6ʀn#kR(7 #\QIYف{]X{̚VPer!Up1 Bvޙ#Jj`[=ϙ|HXn( ^BS7Z+پ# ] >?:&m|ӮFo 9+%GJY,7ֲV(KIӂM諯dp [~{ ĒEӻ rr`F1>*r#nw앙<:@ˉM-q[n9nPc1jm^T%%7r:ȇagZlm`4l݋rNH;p(H/SPKMOi l''flask_simplelogin/__init__.pyPKMO>;<<&'flask_simplelogin/templates/login.htmlPKMO,I,,)v.flask_simplelogin-0.0.7.dist-info/LICENSEPK!HMuSa'2flask_simplelogin-0.0.7.dist-info/WHEELPK!H/*3flask_simplelogin-0.0.7.dist-info/METADATAPK!Hм <(Cflask_simplelogin-0.0.7.dist-info/RECORDPK E