From c0fc823518e112825bb11ed30225f3dec6261406 Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Fri, 25 Oct 2013 12:17:43 +0100 Subject: [PATCH 1/6] Fix missing functions in: google, google_hybrid, google_oauth2, openid, yahoo --- velruse/app/__init__.py | 3 +++ velruse/providers/google.py | 40 ++++++++++++++++++++++++++++-- velruse/providers/google_hybrid.py | 38 +++++++++++++++++++--------- velruse/providers/google_oauth2.py | 23 +++++++++-------- velruse/providers/openid.py | 12 +++++++++ velruse/providers/yahoo.py | 13 ++++++++++ 6 files changed, 105 insertions(+), 24 deletions(-) diff --git a/velruse/app/__init__.py b/velruse/app/__init__.py index 51ec761..b121cda 100644 --- a/velruse/app/__init__.py +++ b/velruse/app/__init__.py @@ -118,6 +118,7 @@ def register_velruse_store(config, storage): 'github': 'add_github_login_from_settings', 'google': 'add_google_login_from_settings', 'google_oauth2': 'add_google_oauth2_login_from_settings', + 'google_hybrid': 'add_google_hybrid_login_from_settings', 'lastfm': 'add_lastfm_login_from_settings', 'linkedin': 'add_linkedin_login_from_settings', 'live': 'add_live_login_from_settings', @@ -126,6 +127,8 @@ def register_velruse_store(config, storage): 'taobao': 'add_taobao_login_from_settings', 'twitter': 'add_twitter_login_from_settings', 'weibo': 'add_weibo_login_from_settings', + 'openid': 'add_openid_login_from_settings', + 'yahoo': 'add_yahoo_login_from_settings', } diff --git a/velruse/providers/google.py b/velruse/providers/google.py index 3ec003f..cd14c8e 100644 --- a/velruse/providers/google.py +++ b/velruse/providers/google.py @@ -1,8 +1,44 @@ """This module exists as a bw-compat shim for google_hybrid.""" from .google_hybrid import ( - add_google_login, - GoogleAuthenticationComplete, + add_google_hybrid_login, ) +from ..settings import ProviderSettings + + def includeme(config): config.add_directive('add_google_login', add_google_login) + config.add_directive('add_google_login_from_settings', + add_google_login_from_settings) + + +def add_google_login_from_settings(config, prefix='velruse.google.'): + settings = config.registry.settings + p = ProviderSettings(settings, prefix) + p.update('consumer_key', required=True) + p.update('consumer_secret', required=True) + p.update('login_path') + p.update('callback_path') + config.add_google_login(**p.kwargs) + + +def add_google_login(config, + attrs=None, + realm=None, + storage=None, + consumer_key=None, + consumer_secret=None, + scope=None, + login_path='/login/google', + callback_path='/login/google/callback', + name='google'): + add_google_hybrid_login(config, + attrs, + realm, + storage, + consumer_key, + consumer_secret, + scope, + login_path, + callback_path, + name) diff --git a/velruse/providers/google_hybrid.py b/velruse/providers/google_hybrid.py index 70b43ad..2146754 100644 --- a/velruse/providers/google_hybrid.py +++ b/velruse/providers/google_hybrid.py @@ -18,6 +18,7 @@ OpenIDConsumer, ) +from ..settings import ProviderSettings log = __import__('logging').getLogger(__name__) @@ -38,18 +39,31 @@ def includeme(config): for the supported options. """ - config.add_directive('add_google_hybrid_login', add_google_login) - -def add_google_login(config, - attrs=None, - realm=None, - storage=None, - consumer_key=None, - consumer_secret=None, - scope=None, - login_path='/login/google', - callback_path='/login/google/callback', - name='google'): + config.add_directive('add_google_hybrid_login', add_google_hybrid_login) + config.add_directive('add_google_hybrid_login_from_settings', + add_google_hybrid_login_from_settings) + + +def add_google_hybrid_login_from_settings(config, prefix='velruse.google_hybrid.'): + settings = config.registry.settings + p = ProviderSettings(settings, prefix) + p.update('consumer_key', required=True) + p.update('consumer_secret', required=True) + p.update('login_path') + p.update('callback_path') + config.add_google_hybrid_login(**p.kwargs) + + +def add_google_hybrid_login(config, + attrs=None, + realm=None, + storage=None, + consumer_key=None, + consumer_secret=None, + scope=None, + login_path='/login/google_hybrid', + callback_path='/login/google_hybrid/callback', + name='google_hybrid'): """ Add a Google login provider to the application using the OpenID+OAuth hybrid protocol. This protocol can be configured for purely diff --git a/velruse/providers/google_oauth2.py b/velruse/providers/google_oauth2.py index 06f59e1..f1c7ce4 100644 --- a/velruse/providers/google_oauth2.py +++ b/velruse/providers/google_oauth2.py @@ -22,6 +22,7 @@ class GoogleAuthenticationComplete(AuthenticationComplete): """Google OAuth 2.0 auth complete""" + def includeme(config): """Activate the ``google_oauth2`` Pyramid plugin via ``config.include('velruse.providers.google_oauth2')``. After included, @@ -34,11 +35,12 @@ def includeme(config): ``config.add_google_oauth2_login_from_settings()`` """ - config.add_directive('add_google_oauth2_login', add_google_login) + config.add_directive('add_google_oauth2_login', add_google_oauth2_login) config.add_directive('add_google_oauth2_login_from_settings', - add_google_login_from_settings) + add_google_oauth2_login_from_settings) + -def add_google_login_from_settings(config, prefix='velruse.google.'): +def add_google_oauth2_login_from_settings(config, prefix='velruse.google_oauth2.'): settings = config.registry.settings p = ProviderSettings(settings, prefix) p.update('consumer_key', required=True) @@ -48,13 +50,14 @@ def add_google_login_from_settings(config, prefix='velruse.google.'): p.update('callback_path') config.add_google_oauth2_login(**p.kwargs) -def add_google_login(config, - consumer_key=None, - consumer_secret=None, - scope=None, - login_path='/login/google', - callback_path='/login/google/callback', - name='google'): + +def add_google_oauth2_login(config, + consumer_key=None, + consumer_secret=None, + scope=None, + login_path='/login/google_oauth2', + callback_path='/login/google_oauth2/callback', + name='google_oauth2'): """ Add a Google login provider to the application supporting the new OAuth2 protocol. diff --git a/velruse/providers/openid.py b/velruse/providers/openid.py index ae35bfa..fffe8d4 100644 --- a/velruse/providers/openid.py +++ b/velruse/providers/openid.py @@ -21,6 +21,8 @@ ThirdPartyFailure, ) +from ..settings import ProviderSettings + log = __import__('logging').getLogger(__name__) # Setup our attribute objects that we'll be requesting @@ -78,6 +80,16 @@ class OpenIDAuthenticationComplete(AuthenticationComplete): def includeme(config): config.add_directive('add_openid_login', add_openid_login) + config.add_directive('add_openid_login_from_settings', + add_openid_login_from_settings) + + +def add_openid_login_from_settings(config, prefix='velruse.openid.'): + settings = config.registry.settings + p = ProviderSettings(settings, prefix) + p.update('login_path') + p.update('callback_path') + config.add_openid_login(**p.kwargs) def add_openid_login(config, diff --git a/velruse/providers/yahoo.py b/velruse/providers/yahoo.py index a260953..8747765 100644 --- a/velruse/providers/yahoo.py +++ b/velruse/providers/yahoo.py @@ -16,6 +16,7 @@ OpenIDConsumer, ) +from ..settings import ProviderSettings log = __import__('logging').getLogger(__name__) @@ -29,6 +30,18 @@ class YahooAuthenticationComplete(OpenIDAuthenticationComplete): def includeme(config): config.add_directive('add_yahoo_login', add_yahoo_login) + config.add_directive('add_yahoo_login_from_settings', + add_yahoo_login_from_settings) + + +def add_yahoo_login_from_settings(config, prefix='velruse.yahoo.'): + settings = config.registry.settings + p = ProviderSettings(settings, prefix) + p.update('consumer_key', required=True) + p.update('consumer_secret', required=True) + p.update('login_path') + p.update('callback_path') + config.add_yahoo_login(**p.kwargs) def add_yahoo_login(config, From c2316346f19b71c700f012e6452a126c3c61b34e Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Sat, 26 Oct 2013 07:19:19 +0100 Subject: [PATCH 2/6] Added example kotti_velruse --- examples/kotti_velruse/CHANGES.txt | 4 + examples/kotti_velruse/Kotti.db | Bin 0 -> 24576 bytes examples/kotti_velruse/MANIFEST.in | 2 + examples/kotti_velruse/README.rst | 0 examples/kotti_velruse/development.ini | 206 ++++++++++ .../kotti_velruse/kotti_velruse/__init__.py | 33 ++ .../kotti_velruse/static/ATTIC/favicon.ico | Bin 0 -> 1406 bytes .../kotti_velruse/static/ATTIC/footerbg.png | Bin 0 -> 333 bytes .../kotti_velruse/static/ATTIC/headerbg.png | Bin 0 -> 203 bytes .../kotti_velruse/static/ATTIC/ie6.css | 8 + .../kotti_velruse/static/ATTIC/middlebg.png | Bin 0 -> 2797 bytes .../kotti_velruse/static/ATTIC/pylons.css | 372 ++++++++++++++++++ .../static/ATTIC/pyramid-small.png | Bin 0 -> 7044 bytes .../kotti_velruse/static/ATTIC/pyramid.png | Bin 0 -> 33055 bytes .../static/ATTIC/transparent.gif | Bin 0 -> 49 bytes .../kotti_velruse/templates/ATTIC/login.mako | 41 ++ .../kotti_velruse/templates/login.mako | 49 +++ .../kotti_velruse/templates/result.mako | 14 + examples/kotti_velruse/kotti_velruse/tests.py | 20 + examples/kotti_velruse/kotti_velruse/views.py | 77 ++++ examples/kotti_velruse/run-server.sh | 16 + examples/kotti_velruse/setup.cfg | 27 ++ examples/kotti_velruse/setup.py | 35 ++ 23 files changed, 904 insertions(+) create mode 100644 examples/kotti_velruse/CHANGES.txt create mode 100644 examples/kotti_velruse/Kotti.db create mode 100644 examples/kotti_velruse/MANIFEST.in create mode 100644 examples/kotti_velruse/README.rst create mode 100644 examples/kotti_velruse/development.ini create mode 100644 examples/kotti_velruse/kotti_velruse/__init__.py create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid.png create mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/transparent.gif create mode 100644 examples/kotti_velruse/kotti_velruse/templates/ATTIC/login.mako create mode 100644 examples/kotti_velruse/kotti_velruse/templates/login.mako create mode 100644 examples/kotti_velruse/kotti_velruse/templates/result.mako create mode 100644 examples/kotti_velruse/kotti_velruse/tests.py create mode 100644 examples/kotti_velruse/kotti_velruse/views.py create mode 100755 examples/kotti_velruse/run-server.sh create mode 100644 examples/kotti_velruse/setup.cfg create mode 100644 examples/kotti_velruse/setup.py diff --git a/examples/kotti_velruse/CHANGES.txt b/examples/kotti_velruse/CHANGES.txt new file mode 100644 index 0000000..fa53044 --- /dev/null +++ b/examples/kotti_velruse/CHANGES.txt @@ -0,0 +1,4 @@ +0.1 +--- + +- Initial version diff --git a/examples/kotti_velruse/Kotti.db b/examples/kotti_velruse/Kotti.db new file mode 100644 index 0000000000000000000000000000000000000000..23e5b4500df16de86b2744056ba10d92e68b5550 GIT binary patch literal 24576 zcmeHP&2JmW72hTGVKKIov~`%EH9RKlN<_sk(X?!Zl0z$0!b+BG$*$@e#$vTQBsW^_ ztaoQcvqliePI78oUQ-oreFn;lKAvKw_+S0iW_f`l#e-Df5rZ z<4_up{|oEqkOKYsG4TcZ33fe3+@iNNb<*{@B(TVmo2yvy-_O7IuGA_O7?jyVEn z<=ExYe4T3blD?x~okRN%&FaTo|46+Efe3*<0=TG;?7xpkl!_2INeKAs|0h!X$w|^W z(ltWhl_RhplTS;@{qZSjT1rb${+HwbmEbRWMF>O)oD2j`#gkLQH~9AdtrY+EWGEhK z86glM;3E)^OH;W1k4dH^nX$E(jmxu#azZ*UNuPgmCiAAwG_OT1{@_8XVw$X-nkD{{ zg|#i};MJZ%+o^}MeT1OWHRdC{)p~(&P(avAKY{54)b^g-=2Jo^M4G# z|NAET2JmJ3zes+Bz^g_eF*cQyg#(CsVD|R^4z~3TM02I#mBV@S!c$ zFc00-aF}PibXApN}->gC{BFOo96mAP2&1L zE?<)5OEE3>#q(O;@x1?Od2%wDOv;~b3-hp;PF?YKDr6KQYlJ=Uj@`G1;sJZMRR=ah zO5Yg>V_~j=!?NcfFi%l6Qw(6d2$@*vfXKrZg14xa#b8eZQhhtXyiwyfXxJiTkZi*&I7$COB?Pfa8jlSd6^QBx_1kEy3@YBG8Ly!;vX zC2-x~0XU%I_C8OC@b2Y~fLy0_)ib$*yR^U=08XehbfV@Mw!jx10_K^p>SO7}PEJ zb;X`^Ak)tkTdg3sD^?roN>F%<=|M;L$4s{5t1L>WSwxKsDcZugxofsBTF^7^p)iEgVU@b68-zHT{C-@J` zCl9`5GgOn<4%0o2yIGh~_h9L8_y7$-7`&4%mUwIn$Tnsddb?}j%jGapHaM;zc#d_lrPRl{J&#v z9Fckv0;3SX_+P~T8--9*79nta5%Bl_UrX}Wv46*BkFTC5PqU|QB_@-XFU!whyAOM5 zlWD4{_yMrN^qJ6}daz`~z8ZJy2V)hzpdoByhs6EFgNGLl+u-&>ncWA?!b-avlN2&$ zNnf7W%nxpeyZ0QiD^8^6UztAOjJjUIf)OeoF@fLc`!2gyTSbpdU4X0)PJ>&HpFQ zJ46~s2p|G@HeZf^gUPr34@^d{2!RNJxA7=}O;{~Lx%lougz zG7!M|e@wnF$$yLeA^vAbM6VZvz<%tF$>cll$ooLFgYcWt0$Pddzn=?*i~PJ1@uD9X z;OT~+M098W%9+XJg$wf2dC`d8nF}{aycudrkUgR`J528c{38rfDWND8@R~5J^#j{{ zASm^8`s`#flaW9518st-$eGfsb@2K}Ater$qArj(5UvdI`np1oDz*<$utN8SwDg}b z3*-YqS^|NGB9nfVP+$^U8xS}L!*&B=2I)OL48%Q&^o`dil3SUhVwor$=#v3(KHYOL z=f~0d|HYVlBqc)N{}ln`J#`rHrf}*Y-2-3=(tVY4_EP_1AdTf^m1w|>b{A7S+#>Lw zF|0aE1@*f!nhvcmrkb4F3%Q&i)@OkxsRKC+rW`lRoJNjXIUqezm*;f42j5oNz}U)x zx2qcs4;WFvSIstg%S`p#(v`hDSVwTYR5SGaa!&2v6lhb<>BgR)Uxrjq4Ht-GR|XzZ zZq6-LbUg!f*>Vojf#mW5Bq{{X1MK+3GC1aiHxQs`u{J`BhvevE;Iit1kXH9BV7oI* z1xmi)mIewS0H zwJ7>(0FEx^KJAQj^$c2zrO%D_``v?~qR&TTzRlodgG1U)`03gfbEtq?Q!Sz+v5zt!PF`_uBc^rxu{3G^sytN~8fAKX1h zf=o{wyN8EQm-?9^z8e*XP*O|qA!Mja=v{;dLEj;U0@750A?k1Vrv+Lh)9_h_gbhKAn0fI4s!?~o1$}{XWF$oF)9p9%- z1G8Bi5JR27Wx*^08aaR@9Cou{OX0afS4IQCG21NyM%wo^7Z-8 zV&l^9(qMPOO_z-p3Xp3xI5V=2Xan%nd2=xZ)xj}Pl(d*C&R&p5U5yDc;^D}h zsS9R%6DGY!9=HZL2)WR!2ot#jLwD?p0h#@gi_2SpK5m@ATX)`k{Svc(;W*1zT9 zug1^M&j+e^JPk^4Bw^QYn)MCQFdt<=?*tT>LgX zG)o`D+QXz-fDIZKvK~{{gsS^zr}z literal 0 HcmV?d00001 diff --git a/examples/kotti_velruse/MANIFEST.in b/examples/kotti_velruse/MANIFEST.in new file mode 100644 index 0000000..7d7e671 --- /dev/null +++ b/examples/kotti_velruse/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include kotti_velruse *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/examples/kotti_velruse/README.rst b/examples/kotti_velruse/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/examples/kotti_velruse/development.ini b/examples/kotti_velruse/development.ini new file mode 100644 index 0000000..ffca094 --- /dev/null +++ b/examples/kotti_velruse/development.ini @@ -0,0 +1,206 @@ +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 6543 + + + +[app:kotti_velruse] +use = egg:kotti_velruse + +pyramid.reload_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +#pyramid.includes = +# pyramid_debugtoolbar + +# By default, the toolbar only appears for clients from IP addresses +# '127.0.0.1' and '::1'. + +# you should change this +session.secret=CHANGE-ME + + + + + +### -------------------------------------------------------------------------- +# [dummy] Kotti configuration +# These settings are necessary when testing plugins in isolation. +### + +[filter:fanstatic] +use = egg:fanstatic#fanstatic + +[pipeline:main] +pipeline = fanstatic + kotti + +[app:kotti] +use = egg:kotti + +pyramid.reload_templates = true +pyramid.debug_templates = true +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +pyramid.includes = pyramid_debugtoolbar + pyramid_tm + +sqlalchemy.url = sqlite:///%(here)s/Kotti.db +#mail.default_sender = yourname@yourhost + +kotti.site_title = My Kotti site +kotti.secret = qwerty + +kotti.configurators = kotti_tinymce.kotti_configure + kotti_velruse.kotti_configure + +kotti.includes = kotti_velruse + +### -------------------------------------------------------------------------- + + + + +### -------------------------------------------------------------------------- +# velruse configuration +# +# Module velruse.app.includeme looks for entries named "provider." in order +# to discover which providers are configured. +# +# NOTE: these configurations must be inside [app:kotti] +# +# Icons copied from: +# https://github.com/diversen/openid-selector +### + +realm=http://apps.xkbm.net + +endpoint = %(realm)s:6543/logged_in +store = memory +# store = redis +# store.host = localhost +# store.port = 6379 +# store.db = 0 +# store.key_prefix = velruse_ustore + +# OpenID +# Despite a single provide.openid is declared, you can specify multiple +# URLs that should be used for connecting to multiple OpenID endpoints. +# See: login.mako for an example of how this can be done +provider.openid.realm=%(realm)s +provider.openid.store=openid.store.memstore:MemoryStore + +# Google (this an alias to Google Hybrid, for backward compatibility) +#provider.google.realm=%(realm)s +#provider.google.consumer_key=CHANGE-ME +#provider.google.consumer_secret=CHANGE-ME +#provider.google.scope=CHANGE-ME + +# Google Hybrid +#provider.google_hybrid.realm=%(realm)s +#provider.google_hybrid.consumer_key=CHANGE-ME +#provider.google_hybrid.consumer_secret=CHANGE-ME +#provider.google_hybrid.scope=CHANGE-ME + +# Google OAuth2 +provider.google_oauth2.consumer_key=CHANGE-ME +provider.google_oauth2.consumer_secret=CHANGE-ME +provider.google_oauth2.scope=CHANGE-ME + +# Yahoo +provider.yahoo.realm=%(realm)s +provider.yahoo.consumer_key=CHANGE-ME +provider.yahoo.consumer_secret=CHANGE-ME + +# Live +provider.live.client_id=CHANGE-ME +provider.live.client_secret=CHANGE-ME +provider.live.consumer_key=CHANGE-ME +provider.live.consumer_secret=CHANGE-ME + +# Twitter +provider.twitter.consumer_key=CHANGE-ME +provider.twitter.consumer_secret=CHANGE-ME + +# Facebook +provider.facebook.app_id=CHANGE-ME +provider.facebook.app_secret=CHANGE-ME +provider.facebook.consumer_key=CHANGE-ME +provider.facebook.consumer_secret=CHANGE-ME + +# LinkedIn +provider.linkedin.consumer_key=CHANGE-ME +provider.linkedin.consumer_secret=CHANGE-ME + +# Github +provider.github.app_id=CHANGE-ME +provider.github.app_secret=CHANGE-ME +provider.github.consumer_key=CHANGE-ME +provider.github.consumer_secret=CHANGE-ME + +# BitBucket +provider.bitbucket.consumer_key=CHANGE-ME +provider.bitbucket.consumer_secret=CHANGE-ME + +### -------------------------------------------------------------------------- + + + + +### -------------------------------------------------------------------------- +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, kotti, velruse, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_kotti] +level = DEBUG +handlers = +qualname = kotti + +[logger_velruse] +level = DEBUG +handlers = +qualname = kotti_velruse + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +### -------------------------------------------------------------------------- diff --git a/examples/kotti_velruse/kotti_velruse/__init__.py b/examples/kotti_velruse/kotti_velruse/__init__.py new file mode 100644 index 0000000..4209645 --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/__init__.py @@ -0,0 +1,33 @@ +from pyramid.config import Configurator + +import velruse.app + + +log = __import__('logging').getLogger(__name__) + + +def includeme(config): + velruse.app.includeme(config) + config.scan() + + +def kotti_configure(settings): + log.info('providers = {}'.format( velruse.app.find_providers(settings) )) + settings['pyramid.includes'] += ' kotti_velruse.views' + + +#def main(global_conf, **settings): +# """ This function returns a Pyramid WSGI application. +# """ +# config = Configurator(settings=settings) +# +# config.add_static_view('static', 'static', cache_max_age=3600) +# config.add_route('home', '/') +# config.add_route('login', '/login') +# config.add_route('logged_in', '/logged_in') +# +# # wires velruse +# velruse.app.includeme(config) +# +# config.scan() +# return config.make_wsgi_app() diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico b/examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..71f837c9e27a57cc290a775b8260241d456582e9 GIT binary patch literal 1406 zcmZQzU<5(|0R}M0U}azs1F|%L7$l?s#Ec9aKoZP=&}eKW1$hQXJ8K3_byWuK^4)pUzxN(#<8UmvsK;IDHKmiOqXh0GT$f5y8Mn(lr zPEIj#Ai)ddFf%g)`Eo2Q!azQdBPb}Sz$wKF1QMLQK#sJuG@G&_7$~s=Ib0wh3I<>% w6A18w0hlQO2J+n8d=Qop8c;z43^FJH7?vVPfPvuvGXp~dBk4g5(gV^90E$0CbN~PV literal 0 HcmV?d00001 diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbc873daa930207b3a5a07a4d34a9478241d67e GIT binary patch literal 333 zcmV-T0kZyyP)x;P*EWNYZUli+~q;(eTbfe*U$baTG!fAgG=`DK4DzIF9EWa~YA`tJ9_ z8KSNH@Hyb?@aX8R^MT1t_v-D!{?^ltv3)o9> f@a++B;w^4}o%yp?Jw|+(00000NkvXXu0mjfL|da= literal 0 HcmV?d00001 diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png new file mode 100644 index 0000000000000000000000000000000000000000..0596f2020327efd97a4467c3025691844bb703d5 GIT binary patch literal 203 zcmV;+05t!JP)t-jh%+|_^FEwkAv~hm;c(PdXHHPSc-$gT+Ec53X`vdosFfm>3bJhR=002ovPDHLk FV1j%>T7Uom literal 0 HcmV?d00001 diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css b/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css new file mode 100644 index 0000000..b7c8493 --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png new file mode 100644 index 0000000000000000000000000000000000000000..2369cfb7da3e5052c2ad4932a6d56240c92654c7 GIT binary patch literal 2797 zcmV4Tx0C)kNmUmQBSrfqTdoR7v5<-y@dJRoV0Fe@UkzPe5BmqJR7!t5oLbpCc6HqI?@={d8%D5al;0(=!Cz zYydD6nO!2_rJ!tuGDRE_#zA==00c_%EKZ!o62USwPXIWXSj`sE}8w<4jU*%sHzk2;U z$a?$5<7MdQoaVsz{O95^ejS$UX;36cb2fe1Y+3Y{{cC>d?Hh%b}~Geu0H=$|_LAH!zl zAj2Q0Uf9TEuaUC0Snjw2jC3cfEVxw!5{*}g2jLb zQa}a}gIur*tOxm^5bOYZKsl%aHJ}bOfD@nvoCX)bWpEwb1byH>7z88W8JGmG!3+dJ zc!&zoAT>xEGJwn=8;A|fhrFObC=7~)5};&A1WBP)&_<{bDu&9TgHRpxBXkP709}Q8 zpu5lzG!Fdvqf1r2MCzX|yZIz>xmnl~$ zpHUuUAPhr>A0wSn#5lp|XS`F#WweHcflJworSw_BrjROl77!Go4w=>|jpnXz2LrNOcbC zbnDFM8tF#rZqRMieW*v$W9ud9?bd78o7C6V57J+yU$1}9fM~!rNHN%J&}lGjXk-{| zxY@A9aLh>6$j@knQN7UvW2&*M@lxYz|7l}t!?UTdxjmOU*L&{Txvg_w*qYf2Z1>yVv7^}q*=@FKxBFo4U@x|B zupf8OcSvxkbQoaM*&*z0>?@8~M-Rufj;9^pI@vo(oK86X;mmSQb3W=kHqU6DU|!9< zVHaH&uFFA}!THSj3G)xkA9U4m<+@h8K6cY{%Av^?0i=GocG202Kesu9q`liwb@Yi zqU=@)9sQZ=k{U}lNr!Ug=Tzjp$&JcAxlD1HXj#{C)8$*2kFM}u@%>87O5V!$RXVHI zuNqqIzWU%AXiegp_O*Iz^VW{6^I3OfJ!yT~`d>C!Z7AOGYGd@qwmi+eb$P>^d^XkR z%jJvn2R1uzuG)gxBHYrwb?(-(tse{c1=k9#3QG##Z{uyd_MP>2rQdzpp0vHY$i8U* z4%`mWj{cplJC77A7OyBC-W9Z~c{g)+!R}Xkmh8D&Vp~$Rm$X;9cd#_Dw6#pXY)9Gq z@|5zv3Xh7$N{z~`mDBt9`+E1g?Qf{ktSYQ}cR+aH&Ox7p&DDn0C5Lc_at=MIiK^-R zp8b7Yt$J-??T5pn!-Ge{j&#&H)YTo;I9gN>*GucikHsIm`Ge;VtqrV(gN=;F!sFn$ z^!U>s6MpPJ5pbgYB>QB;PX<3#Hqn|2nxW?9&66!DErYGGtv#pwPqnu>w>AB2@$=!+ zI;ShnD4!`hOFEl(_S3l)=cdkQou9and||kKN&EeaF&A%lgm!da3b=ITviIeSo$j6I zuDDz|ebwpescYO08?84TZ?^T!>p9!&+I!)a=dH`P z{cd0HThQ0jAK8CrAbw!*4*$;B-SoRJ?&aK@xxelK_Cdizg@+}NG#*v|YVvF2p#9*P zAK5^Wa`oDjMp>M1#i^e9C^!r+xaf~-RMm2 zd;I&-4<;YlJ_dYz@G0Zdr@sILoAdna&gY5%000SaNLh0L01FcU01FcV0GgZ_0000` zNkl3M)`0?X^CI%pY5dZ)Ghq6c#BU2mL4m7>^xj0=#gtkGV1kD*#^bxk;#3qK# z1w@EpQ$noku{i^$7!@T*ax+fPAS4hh!X^U%5(tH;2fehL00000NkvXXu0mjf?v`T0 literal 0 HcmV?d00001 diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css b/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css new file mode 100644 index 0000000..4b1c017 --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css @@ -0,0 +1,372 @@ +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td +{ + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; /* 16px */ + vertical-align: baseline; + background: transparent; +} + +body +{ + line-height: 1; +} + +ol, ul +{ + list-style: none; +} + +blockquote, q +{ + quotes: none; +} + +blockquote:before, blockquote:after, q:before, q:after +{ + content: ''; + content: none; +} + +:focus +{ + outline: 0; +} + +ins +{ + text-decoration: none; +} + +del +{ + text-decoration: line-through; +} + +table +{ + border-collapse: collapse; + border-spacing: 0; +} + +sub +{ + vertical-align: sub; + font-size: smaller; + line-height: normal; +} + +sup +{ + vertical-align: super; + font-size: smaller; + line-height: normal; +} + +ul, menu, dir +{ + display: block; + list-style-type: disc; + margin: 1em 0; + padding-left: 40px; +} + +ol +{ + display: block; + list-style-type: decimal-leading-zero; + margin: 1em 0; + padding-left: 40px; +} + +li +{ + display: list-item; +} + +ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl +{ + margin-top: 0; + margin-bottom: 0; +} + +ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir +{ + list-style-type: circle; +} + +ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir +{ + list-style-type: square; +} + +.hidden +{ + display: none; +} + +p +{ + line-height: 1.5em; +} + +h1 +{ + font-size: 1.75em; + line-height: 1.7em; + font-family: helvetica, verdana; +} + +h2 +{ + font-size: 1.5em; + line-height: 1.7em; + font-family: helvetica, verdana; +} + +h3 +{ + font-size: 1.25em; + line-height: 1.7em; + font-family: helvetica, verdana; +} + +h4 +{ + font-size: 1em; + line-height: 1.7em; + font-family: helvetica, verdana; +} + +html, body +{ + width: 100%; + height: 100%; +} + +body +{ + margin: 0; + padding: 0; + background-color: #fff; + position: relative; + font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; +} + +a +{ + color: #1b61d6; + text-decoration: none; +} + +a:hover +{ + color: #e88f00; + text-decoration: underline; +} + +body h1, body h2, body h3, body h4, body h5, body h6 +{ + font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; + font-weight: 400; + color: #373839; + font-style: normal; +} + +#wrap +{ + min-height: 100%; +} + +#header, #footer +{ + width: 100%; + color: #fff; + height: 40px; + position: absolute; + text-align: center; + line-height: 40px; + overflow: hidden; + font-size: 12px; + vertical-align: middle; +} + +#header +{ + background: #000; + top: 0; + font-size: 14px; +} + +#footer +{ + bottom: 0; + background: #000 url(footerbg.png) repeat-x 0 top; + position: relative; + margin-top: -40px; + clear: both; +} + +.header, .footer +{ + width: 750px; + margin-right: auto; + margin-left: auto; +} + +.wrapper +{ + width: 100%; +} + +#top, #top-small, #bottom +{ + width: 100%; +} + +#top +{ + color: #000; + height: 230px; + background: #fff url(headerbg.png) repeat-x 0 top; + position: relative; +} + +#top-small +{ + color: #000; + height: 60px; + background: #fff url(headerbg.png) repeat-x 0 top; + position: relative; +} + +#bottom +{ + color: #222; + background-color: #fff; +} + +.top, .top-small, .middle, .bottom +{ + width: 750px; + margin-right: auto; + margin-left: auto; +} + +.top +{ + padding-top: 40px; +} + +.top-small +{ + padding-top: 10px; +} + +#middle +{ + width: 100%; + height: 100px; + background: url(middlebg.png) repeat-x; + border-top: 2px solid #fff; + border-bottom: 2px solid #b2b2b2; +} + +.app-welcome +{ + margin-top: 25px; +} + +.app-name +{ + color: #000; + font-weight: 700; +} + +.bottom +{ + padding-top: 50px; +} + +#left +{ + width: 350px; + float: left; + padding-right: 25px; +} + +#right +{ + width: 350px; + float: right; + padding-left: 25px; +} + +.align-left +{ + text-align: left; +} + +.align-right +{ + text-align: right; +} + +.align-center +{ + text-align: center; +} + +ul.links +{ + margin: 0; + padding: 0; +} + +ul.links li +{ + list-style-type: none; + font-size: 14px; +} + +form +{ + border-style: none; +} + +fieldset +{ + border-style: none; +} + +input +{ + color: #222; + border: 1px solid #ccc; + font-family: sans-serif; + font-size: 12px; + line-height: 16px; +} + +input[type=text], input[type=password] +{ + width: 205px; +} + +input[type=submit] +{ + background-color: #ddd; + font-weight: 700; +} + +/*Opera Fix*/ +body:before +{ + content: ""; + height: 100%; + float: left; + width: 0; + margin-top: -32767px; +} diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png new file mode 100644 index 0000000000000000000000000000000000000000..a5bc0ade71d3da5eab67391e840ff20448c42cd6 GIT binary patch literal 7044 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 + + + + Auth Page + + + +<%def name="form(name, title, **kw)"> +% if name in providers: +
+ % for k, v in kw.items(): + + % endfor + +
+% else: +
+ +
+% endif + + + + + +${form('google', 'Login with Google', openid_identifier='google.com', use_popup='false')} +${form('google_hybrid', 'Login with Google (Hybrid)', openid_identifier='google.com', use_popup='false')} +${form('google_oauth2', 'Login with Google (OAuth2)', openid_identifier='google.com', use_popup='false')} +${form('yahoo', 'Login with Yahoo', openid_identifier='yahoo.com', oauth='true')} +${form('live', 'Login with Windows Live')} +${form('facebook', 'Login with Facebook', scope='email,publish_stream,read_stream,create_event,offline_access')} +${form('twitter', 'Login with Twitter')} +${form('github', 'Login with Github')} +${form('bitbucket', 'Login with Bitbucket')} + +${form('openid', 'Login with XKBM.NET', openid_identifier='openid.fake.net:6060/id/rgomes', use_popup='false')} +${form('openid', 'Login with Launchpad', openid_identifier='launchpad.net/~frgomes', use_popup='false')} + + + diff --git a/examples/kotti_velruse/kotti_velruse/templates/login.mako b/examples/kotti_velruse/kotti_velruse/templates/login.mako new file mode 100644 index 0000000..f23935f --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/templates/login.mako @@ -0,0 +1,49 @@ + + + + + + ${project} :: Login + + + + + + + + + + + +

Login to ${project}

+ +
+ +
+ Sign-in +
+

Please click your account provider:

+
+
+
+ + +
+
+
+ +

OpenID allows you to log-on to many different websites using a single identity.
+ Find out more about OpenID and + how to get an OpenID enabled account.

+ + diff --git a/examples/kotti_velruse/kotti_velruse/templates/result.mako b/examples/kotti_velruse/kotti_velruse/templates/result.mako new file mode 100644 index 0000000..088a690 --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/templates/result.mako @@ -0,0 +1,14 @@ + + + + + Result Page + + + +
+${ result|n }
+
+ + + diff --git a/examples/kotti_velruse/kotti_velruse/tests.py b/examples/kotti_velruse/kotti_velruse/tests.py new file mode 100644 index 0000000..785bd45 --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/tests.py @@ -0,0 +1,20 @@ +import unittest + +from pyramid import testing + +import velruse.app + + +class ViewTests(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def test_login_view(self): + from .views import login_view + request = testing.DummyRequest() + providers = velruse.app.find_providers(request.registry.settings) + info = login_view(request) + self.assertEqual(info['providers'], providers) diff --git a/examples/kotti_velruse/kotti_velruse/views.py b/examples/kotti_velruse/kotti_velruse/views.py new file mode 100644 index 0000000..47697ee --- /dev/null +++ b/examples/kotti_velruse/kotti_velruse/views.py @@ -0,0 +1,77 @@ +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound + + +log = __import__('logging').getLogger(__name__) + + +def includeme(config): + config.add_route('login', '/login') + config.add_route('logging_in', '/logging_in') + config.add_route('logged_in', '/logged_in') + config.add_route('logout', '/logout') + + config.add_static_view(name='static', path='kotti_velruse:static') + config.add_static_view(name='', path='kotti_velruse:openid-selector') + + +@view_config(route_name='login', + request_method='GET', + renderer='kotti_velruse:templates/login.mako') +def login_view(request): + settings = request.registry.settings + project = settings['kotti.site_title'] + login_url = request.route_url('logging_in') + from velruse.app import find_providers + providers = find_providers(settings) + return { + 'project' : project, + 'login_url': login_url, + 'providers': providers, + } + + +@view_config(route_name='logging_in', + renderer='json') +def logging_in(request): + provider=request.cookies['openid_provider'] + print('--------------------------------------------------------') + print(provider) + #print('+++++++++++++++') + #print(request.params) + #print('+++++++++++++++') + #print('{}'.format(request)) + + from velruse.api import login_url + if provider in [ 'yahoo', 'twitter' ]: + url = login_url(request, provider) + elif 'facebook' == provider: + request.params['scope'] = 'email,publish_stream,read_stream,create_event,offline_access' + elif 'google' == provider: + url = login_url(request, 'google_oauth2') + elif 'winliveid' == provider: + url = login_url(request, 'live') + else: + url = login_url(request, 'openid') + print(url) + print('--------------------------------------------------------') + return HTTPFound(location=url) + +@view_config(route_name='logged_in', + renderer='json') +def logged_in(request): + import requests + token = request.POST['token'] + payload = { 'format': 'json', 'token': token } + response = requests.get(request.host_url + '/auth_info', params=payload) + return { 'result': response.json() } + + +@view_config(route_name='logout', + permission='view') +def logout(request): + from pyramid.security import forget + request.session.invalidate() + request.session.flash('Session logoff.') + headers = forget(request) + return HTTPFound(location=request.route_url('home'), headers=headers) diff --git a/examples/kotti_velruse/run-server.sh b/examples/kotti_velruse/run-server.sh new file mode 100755 index 0000000..dc3d73f --- /dev/null +++ b/examples/kotti_velruse/run-server.sh @@ -0,0 +1,16 @@ +#/bin/bash + +# make sure proxy settings are ignored +`env | fgrep -i _proxy | cut -d= -f1 | xargs echo unset` + +if [ ! -d kotti_velruse/openid-selector ] ;then + which git + if [ $? -ne 0 ] ; then + sudo apt-get install git -y + fi + pushd kotti_velruse + git clone https://github.com/frgomes/openid-selector.git + popd +fi + +pserve development.ini --reload diff --git a/examples/kotti_velruse/setup.cfg b/examples/kotti_velruse/setup.cfg new file mode 100644 index 0000000..0b9799b --- /dev/null +++ b/examples/kotti_velruse/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match = ^test +nocapture = 1 +cover-package = kotti_velruse +with-coverage = 1 +cover-erase = 1 + +[compile_catalog] +directory = kotti_velruse/locale +domain = kotti_velruse +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = kotti_velruse/locale/kotti_velruse.pot +width = 80 + +[init_catalog] +domain = kotti_velruse +input_file = kotti_velruse/locale/kotti_velruse.pot +output_dir = kotti_velruse/locale + +[update_catalog] +domain = kotti_velruse +input_file = kotti_velruse/locale/kotti_velruse.pot +output_dir = kotti_velruse/locale +previous = true diff --git a/examples/kotti_velruse/setup.py b/examples/kotti_velruse/setup.py new file mode 100644 index 0000000..0571dde --- /dev/null +++ b/examples/kotti_velruse/setup.py @@ -0,0 +1,35 @@ +from setuptools import setup, find_packages +import os + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.rst')).read() + +version = '0.1' + +requires = [ + 'Kotti', + ] + +setup(name='kotti_velruse', + version=version, + description="Kotti authentication with Velruse: OpenID, OAuth2, Google, Yahoo, Live, Facebook, Twitter and others", + long_description=README, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + keywords='kotti authentication velruse openid oauth2 google yahoo live facebook twitter', + author='Richard Gomes', + author_email='rgomes.info@gmail.com', + url='http://kotti_velruse.readthedocs.org', + license='BSD', + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=requires, + entry_points=""" + # -*- Entry points: -*- + """, + ) From e2c4b993b15343e7e7290143c1aa988dcf41012b Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Mon, 28 Oct 2013 00:22:27 +0000 Subject: [PATCH 3/6] example kotti_velruse added --- examples/kotti_velruse/Kotti.db | Bin 24576 -> 0 bytes examples/kotti_velruse/README.rst | 26 ++ examples/kotti_velruse/development.ini | 7 +- .../kotti_velruse/kotti_velruse/__init__.py | 25 +- .../kotti_velruse/static/ATTIC/favicon.ico | Bin 1406 -> 0 bytes .../kotti_velruse/static/ATTIC/footerbg.png | Bin 333 -> 0 bytes .../kotti_velruse/static/ATTIC/headerbg.png | Bin 203 -> 0 bytes .../kotti_velruse/static/ATTIC/ie6.css | 8 - .../kotti_velruse/static/ATTIC/middlebg.png | Bin 2797 -> 0 bytes .../kotti_velruse/static/ATTIC/pylons.css | 372 ------------------ .../static/ATTIC/pyramid-small.png | Bin 7044 -> 0 bytes .../kotti_velruse/static/ATTIC/pyramid.png | Bin 33055 -> 0 bytes .../static/ATTIC/transparent.gif | Bin 49 -> 0 bytes .../kotti_velruse/templates/ATTIC/login.mako | 41 -- .../kotti_velruse/templates/login.mako | 7 +- examples/kotti_velruse/kotti_velruse/views.py | 132 ++++--- examples/kotti_velruse/run-server.sh | 16 +- 17 files changed, 126 insertions(+), 508 deletions(-) delete mode 100644 examples/kotti_velruse/Kotti.db delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid.png delete mode 100644 examples/kotti_velruse/kotti_velruse/static/ATTIC/transparent.gif delete mode 100644 examples/kotti_velruse/kotti_velruse/templates/ATTIC/login.mako diff --git a/examples/kotti_velruse/Kotti.db b/examples/kotti_velruse/Kotti.db deleted file mode 100644 index 23e5b4500df16de86b2744056ba10d92e68b5550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeHP&2JmW72hTGVKKIov~`%EH9RKlN<_sk(X?!Zl0z$0!b+BG$*$@e#$vTQBsW^_ ztaoQcvqliePI78oUQ-oreFn;lKAvKw_+S0iW_f`l#e-Df5rZ z<4_up{|oEqkOKYsG4TcZ33fe3+@iNNb<*{@B(TVmo2yvy-_O7IuGA_O7?jyVEn z<=ExYe4T3blD?x~okRN%&FaTo|46+Efe3*<0=TG;?7xpkl!_2INeKAs|0h!X$w|^W z(ltWhl_RhplTS;@{qZSjT1rb${+HwbmEbRWMF>O)oD2j`#gkLQH~9AdtrY+EWGEhK z86glM;3E)^OH;W1k4dH^nX$E(jmxu#azZ*UNuPgmCiAAwG_OT1{@_8XVw$X-nkD{{ zg|#i};MJZ%+o^}MeT1OWHRdC{)p~(&P(avAKY{54)b^g-=2Jo^M4G# z|NAET2JmJ3zes+Bz^g_eF*cQyg#(CsVD|R^4z~3TM02I#mBV@S!c$ zFc00-aF}PibXApN}->gC{BFOo96mAP2&1L zE?<)5OEE3>#q(O;@x1?Od2%wDOv;~b3-hp;PF?YKDr6KQYlJ=Uj@`G1;sJZMRR=ah zO5Yg>V_~j=!?NcfFi%l6Qw(6d2$@*vfXKrZg14xa#b8eZQhhtXyiwyfXxJiTkZi*&I7$COB?Pfa8jlSd6^QBx_1kEy3@YBG8Ly!;vX zC2-x~0XU%I_C8OC@b2Y~fLy0_)ib$*yR^U=08XehbfV@Mw!jx10_K^p>SO7}PEJ zb;X`^Ak)tkTdg3sD^?roN>F%<=|M;L$4s{5t1L>WSwxKsDcZugxofsBTF^7^p)iEgVU@b68-zHT{C-@J` zCl9`5GgOn<4%0o2yIGh~_h9L8_y7$-7`&4%mUwIn$Tnsddb?}j%jGapHaM;zc#d_lrPRl{J&#v z9Fckv0;3SX_+P~T8--9*79nta5%Bl_UrX}Wv46*BkFTC5PqU|QB_@-XFU!whyAOM5 zlWD4{_yMrN^qJ6}daz`~z8ZJy2V)hzpdoByhs6EFgNGLl+u-&>ncWA?!b-avlN2&$ zNnf7W%nxpeyZ0QiD^8^6UztAOjJjUIf)OeoF@fLc`!2gyTSbpdU4X0)PJ>&HpFQ zJ46~s2p|G@HeZf^gUPr34@^d{2!RNJxA7=}O;{~Lx%lougz zG7!M|e@wnF$$yLeA^vAbM6VZvz<%tF$>cll$ooLFgYcWt0$Pddzn=?*i~PJ1@uD9X z;OT~+M098W%9+XJg$wf2dC`d8nF}{aycudrkUgR`J528c{38rfDWND8@R~5J^#j{{ zASm^8`s`#flaW9518st-$eGfsb@2K}Ater$qArj(5UvdI`np1oDz*<$utN8SwDg}b z3*-YqS^|NGB9nfVP+$^U8xS}L!*&B=2I)OL48%Q&^o`dil3SUhVwor$=#v3(KHYOL z=f~0d|HYVlBqc)N{}ln`J#`rHrf}*Y-2-3=(tVY4_EP_1AdTf^m1w|>b{A7S+#>Lw zF|0aE1@*f!nhvcmrkb4F3%Q&i)@OkxsRKC+rW`lRoJNjXIUqezm*;f42j5oNz}U)x zx2qcs4;WFvSIstg%S`p#(v`hDSVwTYR5SGaa!&2v6lhb<>BgR)Uxrjq4Ht-GR|XzZ zZq6-LbUg!f*>Vojf#mW5Bq{{X1MK+3GC1aiHxQs`u{J`BhvevE;Iit1kXH9BV7oI* z1xmi)mIewS0H zwJ7>(0FEx^KJAQj^$c2zrO%D_``v?~qR&TTzRlodgG1U)`03gfbEtq?Q!Sz+v5zt!PF`_uBc^rxu{3G^sytN~8fAKX1h zf=o{wyN8EQm-?9^z8e*XP*O|qA!Mja=v{;dLEj;U0@750A?k1Vrv+Lh)9_h_gbhKAn0fI4s!?~o1$}{XWF$oF)9p9%- z1G8Bi5JR27Wx*^08aaR@9Cou{OX0afS4IQCG21NyM%wo^7Z-8 zV&l^9(qMPOO_z-p3Xp3xI5V=2Xan%nd2=xZ)xj}Pl(d*C&R&p5U5yDc;^D}h zsS9R%6DGY!9=HZL2)WR!2ot#jLwD?p0h#@gi_2SpK5m@ATX)`k{Svc(;W*1zT9 zug1^M&j+e^JPk^4Bw^QYn)MCQFdt<=?*tT>LgX zG)o`D+QXz-fDIZKvK~{{gsS^zr}z diff --git a/examples/kotti_velruse/README.rst b/examples/kotti_velruse/README.rst index e69de29..fa66989 100644 --- a/examples/kotti_velruse/README.rst +++ b/examples/kotti_velruse/README.rst @@ -0,0 +1,26 @@ +Install Kotti +------------- + + $ python setup.py develop + + +Start the server +---------------- + + $ run-server.sh + + +Navigate +-------- + + $ firefox http://localhost:6543/login + + +Notes +----- + +This example program utilizes openid-selector, which is cloned from github when first start the server. + + + + diff --git a/examples/kotti_velruse/development.ini b/examples/kotti_velruse/development.ini index ffca094..c1ed918 100644 --- a/examples/kotti_velruse/development.ini +++ b/examples/kotti_velruse/development.ini @@ -63,7 +63,6 @@ kotti.site_title = My Kotti site kotti.secret = qwerty kotti.configurators = kotti_tinymce.kotti_configure - kotti_velruse.kotti_configure kotti.includes = kotti_velruse @@ -119,9 +118,9 @@ provider.google_oauth2.consumer_secret=CHANGE-ME provider.google_oauth2.scope=CHANGE-ME # Yahoo -provider.yahoo.realm=%(realm)s -provider.yahoo.consumer_key=CHANGE-ME -provider.yahoo.consumer_secret=CHANGE-ME +#provider.yahoo.realm=%(realm)s +#provider.yahoo.consumer_key=CHANGE-ME +#provider.yahoo.consumer_secret=CHANGE-ME # Live provider.live.client_id=CHANGE-ME diff --git a/examples/kotti_velruse/kotti_velruse/__init__.py b/examples/kotti_velruse/kotti_velruse/__init__.py index 4209645..ea60222 100644 --- a/examples/kotti_velruse/kotti_velruse/__init__.py +++ b/examples/kotti_velruse/kotti_velruse/__init__.py @@ -1,6 +1,7 @@ from pyramid.config import Configurator import velruse.app +import views log = __import__('logging').getLogger(__name__) @@ -8,26 +9,4 @@ def includeme(config): velruse.app.includeme(config) - config.scan() - - -def kotti_configure(settings): - log.info('providers = {}'.format( velruse.app.find_providers(settings) )) - settings['pyramid.includes'] += ' kotti_velruse.views' - - -#def main(global_conf, **settings): -# """ This function returns a Pyramid WSGI application. -# """ -# config = Configurator(settings=settings) -# -# config.add_static_view('static', 'static', cache_max_age=3600) -# config.add_route('home', '/') -# config.add_route('login', '/login') -# config.add_route('logged_in', '/logged_in') -# -# # wires velruse -# velruse.app.includeme(config) -# -# config.scan() -# return config.make_wsgi_app() + views.includeme(config) diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico b/examples/kotti_velruse/kotti_velruse/static/ATTIC/favicon.ico deleted file mode 100644 index 71f837c9e27a57cc290a775b8260241d456582e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1406 zcmZQzU<5(|0R}M0U}azs1F|%L7$l?s#Ec9aKoZP=&}eKW1$hQXJ8K3_byWuK^4)pUzxN(#<8UmvsK;IDHKmiOqXh0GT$f5y8Mn(lr zPEIj#Ai)ddFf%g)`Eo2Q!azQdBPb}Sz$wKF1QMLQK#sJuG@G&_7$~s=Ib0wh3I<>% w6A18w0hlQO2J+n8d=Qop8c;z43^FJH7?vVPfPvuvGXp~dBk4g5(gV^90E$0CbN~PV diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/footerbg.png deleted file mode 100644 index 1fbc873daa930207b3a5a07a4d34a9478241d67e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 333 zcmV-T0kZyyP)x;P*EWNYZUli+~q;(eTbfe*U$baTG!fAgG=`DK4DzIF9EWa~YA`tJ9_ z8KSNH@Hyb?@aX8R^MT1t_v-D!{?^ltv3)o9> f@a++B;w^4}o%yp?Jw|+(00000NkvXXu0mjfL|da= diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/headerbg.png deleted file mode 100644 index 0596f2020327efd97a4467c3025691844bb703d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmV;+05t!JP)t-jh%+|_^FEwkAv~hm;c(PdXHHPSc-$gT+Ec53X`vdosFfm>3bJhR=002ovPDHLk FV1j%>T7Uom diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css b/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css deleted file mode 100644 index b7c8493..0000000 --- a/examples/kotti_velruse/kotti_velruse/static/ATTIC/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/middlebg.png deleted file mode 100644 index 2369cfb7da3e5052c2ad4932a6d56240c92654c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2797 zcmV4Tx0C)kNmUmQBSrfqTdoR7v5<-y@dJRoV0Fe@UkzPe5BmqJR7!t5oLbpCc6HqI?@={d8%D5al;0(=!Cz zYydD6nO!2_rJ!tuGDRE_#zA==00c_%EKZ!o62USwPXIWXSj`sE}8w<4jU*%sHzk2;U z$a?$5<7MdQoaVsz{O95^ejS$UX;36cb2fe1Y+3Y{{cC>d?Hh%b}~Geu0H=$|_LAH!zl zAj2Q0Uf9TEuaUC0Snjw2jC3cfEVxw!5{*}g2jLb zQa}a}gIur*tOxm^5bOYZKsl%aHJ}bOfD@nvoCX)bWpEwb1byH>7z88W8JGmG!3+dJ zc!&zoAT>xEGJwn=8;A|fhrFObC=7~)5};&A1WBP)&_<{bDu&9TgHRpxBXkP709}Q8 zpu5lzG!Fdvqf1r2MCzX|yZIz>xmnl~$ zpHUuUAPhr>A0wSn#5lp|XS`F#WweHcflJworSw_BrjROl77!Go4w=>|jpnXz2LrNOcbC zbnDFM8tF#rZqRMieW*v$W9ud9?bd78o7C6V57J+yU$1}9fM~!rNHN%J&}lGjXk-{| zxY@A9aLh>6$j@knQN7UvW2&*M@lxYz|7l}t!?UTdxjmOU*L&{Txvg_w*qYf2Z1>yVv7^}q*=@FKxBFo4U@x|B zupf8OcSvxkbQoaM*&*z0>?@8~M-Rufj;9^pI@vo(oK86X;mmSQb3W=kHqU6DU|!9< zVHaH&uFFA}!THSj3G)xkA9U4m<+@h8K6cY{%Av^?0i=GocG202Kesu9q`liwb@Yi zqU=@)9sQZ=k{U}lNr!Ug=Tzjp$&JcAxlD1HXj#{C)8$*2kFM}u@%>87O5V!$RXVHI zuNqqIzWU%AXiegp_O*Iz^VW{6^I3OfJ!yT~`d>C!Z7AOGYGd@qwmi+eb$P>^d^XkR z%jJvn2R1uzuG)gxBHYrwb?(-(tse{c1=k9#3QG##Z{uyd_MP>2rQdzpp0vHY$i8U* z4%`mWj{cplJC77A7OyBC-W9Z~c{g)+!R}Xkmh8D&Vp~$Rm$X;9cd#_Dw6#pXY)9Gq z@|5zv3Xh7$N{z~`mDBt9`+E1g?Qf{ktSYQ}cR+aH&Ox7p&DDn0C5Lc_at=MIiK^-R zp8b7Yt$J-??T5pn!-Ge{j&#&H)YTo;I9gN>*GucikHsIm`Ge;VtqrV(gN=;F!sFn$ z^!U>s6MpPJ5pbgYB>QB;PX<3#Hqn|2nxW?9&66!DErYGGtv#pwPqnu>w>AB2@$=!+ zI;ShnD4!`hOFEl(_S3l)=cdkQou9and||kKN&EeaF&A%lgm!da3b=ITviIeSo$j6I zuDDz|ebwpescYO08?84TZ?^T!>p9!&+I!)a=dH`P z{cd0HThQ0jAK8CrAbw!*4*$;B-SoRJ?&aK@xxelK_Cdizg@+}NG#*v|YVvF2p#9*P zAK5^Wa`oDjMp>M1#i^e9C^!r+xaf~-RMm2 zd;I&-4<;YlJ_dYz@G0Zdr@sILoAdna&gY5%000SaNLh0L01FcU01FcV0GgZ_0000` zNkl3M)`0?X^CI%pY5dZ)Ghq6c#BU2mL4m7>^xj0=#gtkGV1kD*#^bxk;#3qK# z1w@EpQ$noku{i^$7!@T*ax+fPAS4hh!X^U%5(tH;2fehL00000NkvXXu0mjf?v`T0 diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css b/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css deleted file mode 100644 index 4b1c017..0000000 --- a/examples/kotti_velruse/kotti_velruse/static/ATTIC/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png b/examples/kotti_velruse/kotti_velruse/static/ATTIC/pyramid-small.png deleted file mode 100644 index a5bc0ade71d3da5eab67391e840ff20448c42cd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7044 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 - - - - Auth Page - - - -<%def name="form(name, title, **kw)"> -% if name in providers: -
- % for k, v in kw.items(): - - % endfor - -
-% else: -
- -
-% endif - - - - - -${form('google', 'Login with Google', openid_identifier='google.com', use_popup='false')} -${form('google_hybrid', 'Login with Google (Hybrid)', openid_identifier='google.com', use_popup='false')} -${form('google_oauth2', 'Login with Google (OAuth2)', openid_identifier='google.com', use_popup='false')} -${form('yahoo', 'Login with Yahoo', openid_identifier='yahoo.com', oauth='true')} -${form('live', 'Login with Windows Live')} -${form('facebook', 'Login with Facebook', scope='email,publish_stream,read_stream,create_event,offline_access')} -${form('twitter', 'Login with Twitter')} -${form('github', 'Login with Github')} -${form('bitbucket', 'Login with Bitbucket')} - -${form('openid', 'Login with XKBM.NET', openid_identifier='openid.fake.net:6060/id/rgomes', use_popup='false')} -${form('openid', 'Login with Launchpad', openid_identifier='launchpad.net/~frgomes', use_popup='false')} - - - diff --git a/examples/kotti_velruse/kotti_velruse/templates/login.mako b/examples/kotti_velruse/kotti_velruse/templates/login.mako index f23935f..bf6eba8 100644 --- a/examples/kotti_velruse/kotti_velruse/templates/login.mako +++ b/examples/kotti_velruse/kotti_velruse/templates/login.mako @@ -28,7 +28,8 @@

Login to ${project}

- + +
Sign-in
@@ -42,8 +43,8 @@
-

OpenID allows you to log-on to many different websites using a single identity.
+

OpenID allows you to log-on to many different websites using a single identity.
Find out more about OpenID and - how to get an OpenID enabled account.

+ how to get an OpenID enabled account.

diff --git a/examples/kotti_velruse/kotti_velruse/views.py b/examples/kotti_velruse/kotti_velruse/views.py index 47697ee..f31bfbc 100644 --- a/examples/kotti_velruse/kotti_velruse/views.py +++ b/examples/kotti_velruse/kotti_velruse/views.py @@ -1,77 +1,107 @@ -from pyramid.view import view_config -from pyramid.httpexceptions import HTTPFound +from pyramid.httpexceptions import HTTPFound, HTTPNotFound +from pyramid.request import Request + +from velruse.api import login_url +from velruse.app import find_providers log = __import__('logging').getLogger(__name__) def includeme(config): - config.add_route('login', '/login') - config.add_route('logging_in', '/logging_in') - config.add_route('logged_in', '/logged_in') - config.add_route('logout', '/logout') + config.add_view(login, + route_name='login', + request_method='GET', + renderer='kotti_velruse:templates/login.mako') + config.add_view(login_, + route_name='login_', + renderer='json') + config.add_view(logged_in, + route_name='logged_in', + renderer='json') + config.add_view(logout, + route_name='logout', + permission='view') + + config.add_route('login', '/login') + config.add_route('login_', '/login_') + config.add_route('logged_in', '/logged_in') + config.add_route('logout', '/logout') config.add_static_view(name='static', path='kotti_velruse:static') + + #################################################################################### + # This route named '' MUST BE THE LAST ONE in the global list of routes. + # It means that plugin kotti_velruse MUST BE THE LAST ONE in the list of includes. + # + # It's definitely a bad idea to employ a route named ''. + # But, in order to avoid this, we would have to change openid-selector too much :( + # ... which is outside of our requirements for this demo. + #################################################################################### config.add_static_view(name='', path='kotti_velruse:openid-selector') -@view_config(route_name='login', - request_method='GET', - renderer='kotti_velruse:templates/login.mako') -def login_view(request): +def login(request): settings = request.registry.settings project = settings['kotti.site_title'] - login_url = request.route_url('logging_in') - from velruse.app import find_providers - providers = find_providers(settings) return { 'project' : project, - 'login_url': login_url, - 'providers': providers, + 'login_url': request.route_url('login_'), } -@view_config(route_name='logging_in', - renderer='json') -def logging_in(request): - provider=request.cookies['openid_provider'] - print('--------------------------------------------------------') - print(provider) - #print('+++++++++++++++') - #print(request.params) - #print('+++++++++++++++') - #print('{}'.format(request)) - - from velruse.api import login_url - if provider in [ 'yahoo', 'twitter' ]: - url = login_url(request, provider) - elif 'facebook' == provider: - request.params['scope'] = 'email,publish_stream,read_stream,create_event,offline_access' - elif 'google' == provider: - url = login_url(request, 'google_oauth2') - elif 'winliveid' == provider: - url = login_url(request, 'live') - else: - url = login_url(request, 'openid') - print(url) - print('--------------------------------------------------------') - return HTTPFound(location=url) - -@view_config(route_name='logged_in', - renderer='json') +def login_(request): + #################################################################################### + # Let's clarify the difference between "provider" and "method": + # + # * Conceptually, methods can be understood pretty much like protocols or transports. + # So, methods would be for example: OpenID, OAuth2, CAS, LDAP. + # * A provider is simply an entity, like Verisign, Google, Yahoo, Launchpad and + # hundreds of other entities which employ popular methods like OpenID and OAuth2. + # * In particular, certain entities implement their own methods (or protocols) or + # they eventually offer several authentication methods. For this reason, there are + # specific methods for "yahoo", "tweeter", "google_hybrid", "google_oauth2", etc. + # + # For the SAKE OF SIMPLICITY we arbitrarity consider providers and methods simply + # as entities in this function in particular. + #################################################################################### + provider=request.params['method'] + + settings = request.registry.settings + if not provider in find_providers(settings): + raise HTTPNotFound('Provider "{}" is not configured'.format(provider)).exception + + velruse_url = login_url(request, provider) + + payload = dict(request.params) + if 'yahoo' == provider: payload['oauth'] = 'true' + if 'facebook' == provider: payload['scope'] = 'email,publish_stream,read_stream,create_event,offline_access' + if 'openid' == provider: payload['use_popup'] = 'false' + payload['format'] = 'json' + + redirect = Request.blank(velruse_url, POST=payload) + try: + response = request.invoke_subrequest( redirect ) + return response + except: + message = 'Provider "{}" is probably misconfigured'.format(provider) + raise HTTPNotFound(message).exception + + def logged_in(request): - import requests - token = request.POST['token'] - payload = { 'format': 'json', 'token': token } - response = requests.get(request.host_url + '/auth_info', params=payload) - return { 'result': response.json() } + token = request.params['token'] + storage = request.registry.velruse_store + try: + return storage.retrieve(token) + except KeyError: + message = 'invalid token "{}"'.format(token) + log.error(message) + return { 'error' : message } -@view_config(route_name='logout', - permission='view') def logout(request): from pyramid.security import forget request.session.invalidate() request.session.flash('Session logoff.') headers = forget(request) - return HTTPFound(location=request.route_url('home'), headers=headers) + return HTTPFound(location=request.route_url('login'), headers=headers) diff --git a/examples/kotti_velruse/run-server.sh b/examples/kotti_velruse/run-server.sh index dc3d73f..ebafd27 100755 --- a/examples/kotti_velruse/run-server.sh +++ b/examples/kotti_velruse/run-server.sh @@ -4,13 +4,17 @@ `env | fgrep -i _proxy | cut -d= -f1 | xargs echo unset` if [ ! -d kotti_velruse/openid-selector ] ;then - which git - if [ $? -ne 0 ] ; then - sudo apt-get install git -y + if [ -d ~/sources/frgomes/openid-selector/master/openid-selector ] ;then + ln -s ~/sources/frgomes/openid-selector/master/openid-selector kotti_velruse/openid-selector + else + which git + if [ $? -ne 0 ] ; then + sudo apt-get install git -y + fi + pushd kotti_velruse + git clone https://github.com/frgomes/openid-selector.git + popd fi - pushd kotti_velruse - git clone https://github.com/frgomes/openid-selector.git - popd fi pserve development.ini --reload From 54b51b2c6930782ce504154c39a16db21036eab2 Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Tue, 29 Oct 2013 00:43:24 +0000 Subject: [PATCH 4/6] adding support for russian providers --- velruse/app/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/velruse/app/__init__.py b/velruse/app/__init__.py index b121cda..0b02deb 100644 --- a/velruse/app/__init__.py +++ b/velruse/app/__init__.py @@ -129,6 +129,9 @@ def register_velruse_store(config, storage): 'weibo': 'add_weibo_login_from_settings', 'openid': 'add_openid_login_from_settings', 'yahoo': 'add_yahoo_login_from_settings', + 'mailru': 'add_mailru_login_from_settings', + 'yandex': 'add_yandex_login_from_settings', + 'vk': 'add_vk_login_from_settings', } From b64304daed1ce1c07c2783a19ab1f76c9638c342 Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Tue, 29 Oct 2013 16:21:41 +0000 Subject: [PATCH 5/6] bump version to 1.1.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2bf6aec..4e14a99 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ README = CHANGES = '' setup(name='velruse', - version='1.1.1', + version='1.1.2', description=( 'Simplifying third-party authentication for web applications.'), long_description=README + '\n\n' + CHANGES, From d2a4875ae53b94651d0b2d01ddd1269e2f4fa59a Mon Sep 17 00:00:00 2001 From: Richard Gomes Date: Wed, 30 Oct 2013 22:55:34 +0000 Subject: [PATCH 6/6] making the example closer to plugin already published on PyPI --- examples/kotti_velruse/README.rst | 39 ++++-- examples/kotti_velruse/development.ini | 40 +++--- .../kotti_velruse/kotti_velruse/__init__.py | 14 +-- examples/kotti_velruse/kotti_velruse/views.py | 116 ++++++++++-------- examples/kotti_velruse/run-server.sh | 36 +++--- examples/kotti_velruse/setup.py | 4 +- 6 files changed, 149 insertions(+), 100 deletions(-) diff --git a/examples/kotti_velruse/README.rst b/examples/kotti_velruse/README.rst index fa66989..67f843a 100644 --- a/examples/kotti_velruse/README.rst +++ b/examples/kotti_velruse/README.rst @@ -1,26 +1,43 @@ -Install Kotti +For the impatient +----------------- + +1. Simply run script run-server.sh + +2. Navigate to page /login like the example below: + + $ firefox http://localhost:6543/login + + +Configuration ------------- - $ python setup.py develop +1. Please adjust variable *realm* in development.ini. +2. Several providers need to be configured according to your affiliation + keys with providers like Google OAuth2, Twitter, Facebook, etc. -Start the server ----------------- +Several providers work out of the box, like Google Hybrid, Yahoo and most +of OpenID providers. - $ run-server.sh +About this example +------------------ -Navigate --------- +This example evolved to a proper plugin, which is available from PyPI at +https://pypi.python.org/pypi/kotti_velruse - $ firefox http://localhost:6543/login +Dependencies +------------ -Notes ------ +This example depends on a modified versions of velruse and openid-selector: -This example program utilizes openid-selector, which is cloned from github when first start the server. +* velruse: https://pypi.python.org/pypi/rgomes_velruse +* openid-selector: https://pypi.python.org/pypi/openid-selector +Sources for these changed sources are available at: +* velruse: https://github.com/frgomes/velruse/tree/feature.kotti_auth +* openid-selector: https://github.com/frgomes/velruse diff --git a/examples/kotti_velruse/development.ini b/examples/kotti_velruse/development.ini index c1ed918..f407716 100644 --- a/examples/kotti_velruse/development.ini +++ b/examples/kotti_velruse/development.ini @@ -63,8 +63,7 @@ kotti.site_title = My Kotti site kotti.secret = qwerty kotti.configurators = kotti_tinymce.kotti_configure - -kotti.includes = kotti_velruse + kotti_velruse.kotti_configure ### -------------------------------------------------------------------------- @@ -79,11 +78,18 @@ kotti.includes = kotti_velruse # # NOTE: these configurations must be inside [app:kotti] # -# Icons copied from: -# https://github.com/diversen/openid-selector ### -realm=http://apps.xkbm.net +#--- +# Please adjust variable REALM +# +# Make sure that: +# +# 1. your browser is able to resolve the FQDN +# 2. your Kotti server is able to resolve the FQDN +# +#--- +realm=http://www.example.com endpoint = %(realm)s:6543/logged_in store = memory @@ -101,10 +107,10 @@ provider.openid.realm=%(realm)s provider.openid.store=openid.store.memstore:MemoryStore # Google (this an alias to Google Hybrid, for backward compatibility) -#provider.google.realm=%(realm)s -#provider.google.consumer_key=CHANGE-ME -#provider.google.consumer_secret=CHANGE-ME -#provider.google.scope=CHANGE-ME +provider.google.realm=%(realm)s +provider.google.consumer_key=CHANGE-ME +provider.google.consumer_secret=CHANGE-ME +provider.google.scope=CHANGE-ME # Google Hybrid #provider.google_hybrid.realm=%(realm)s @@ -118,9 +124,9 @@ provider.google_oauth2.consumer_secret=CHANGE-ME provider.google_oauth2.scope=CHANGE-ME # Yahoo -#provider.yahoo.realm=%(realm)s -#provider.yahoo.consumer_key=CHANGE-ME -#provider.yahoo.consumer_secret=CHANGE-ME +provider.yahoo.realm=%(realm)s +provider.yahoo.consumer_key=CHANGE-ME +provider.yahoo.consumer_secret=CHANGE-ME # Live provider.live.client_id=CHANGE-ME @@ -137,21 +143,27 @@ provider.facebook.app_id=CHANGE-ME provider.facebook.app_secret=CHANGE-ME provider.facebook.consumer_key=CHANGE-ME provider.facebook.consumer_secret=CHANGE-ME +provider.facebook.scope=email,publish_stream,read_stream,create_event,offline_access # LinkedIn provider.linkedin.consumer_key=CHANGE-ME provider.linkedin.consumer_secret=CHANGE-ME # Github -provider.github.app_id=CHANGE-ME -provider.github.app_secret=CHANGE-ME provider.github.consumer_key=CHANGE-ME provider.github.consumer_secret=CHANGE-ME +provider.github.scope=CHANGE-ME # BitBucket provider.bitbucket.consumer_key=CHANGE-ME provider.bitbucket.consumer_secret=CHANGE-ME +# MailRU +provider.mailru.app_id=CHANGE-ME +provider.mailru.app_secret=CHANGE-ME +provider.mailru.consumer_key=CHANGE-ME +provider.mailru.consumer_secret=CHANGE-ME + ### -------------------------------------------------------------------------- diff --git a/examples/kotti_velruse/kotti_velruse/__init__.py b/examples/kotti_velruse/kotti_velruse/__init__.py index ea60222..f3286c5 100644 --- a/examples/kotti_velruse/kotti_velruse/__init__.py +++ b/examples/kotti_velruse/kotti_velruse/__init__.py @@ -1,12 +1,12 @@ -from pyramid.config import Configurator - -import velruse.app -import views +from pyramid.i18n import TranslationStringFactory log = __import__('logging').getLogger(__name__) -def includeme(config): - velruse.app.includeme(config) - views.includeme(config) +_ = TranslationStringFactory('kotti_velruse') + + +def kotti_configure(settings): + settings['pyramid.includes'] += ' velruse.app' + settings['pyramid.includes'] += ' kotti_velruse.views' diff --git a/examples/kotti_velruse/kotti_velruse/views.py b/examples/kotti_velruse/kotti_velruse/views.py index f31bfbc..fcba91d 100644 --- a/examples/kotti_velruse/kotti_velruse/views.py +++ b/examples/kotti_velruse/kotti_velruse/views.py @@ -4,8 +4,7 @@ from velruse.api import login_url from velruse.app import find_providers - -log = __import__('logging').getLogger(__name__) +from kotti_velruse import log, _ def includeme(config): @@ -28,80 +27,95 @@ def includeme(config): config.add_route('logged_in', '/logged_in') config.add_route('logout', '/logout') - config.add_static_view(name='static', path='kotti_velruse:static') - - #################################################################################### - # This route named '' MUST BE THE LAST ONE in the global list of routes. - # It means that plugin kotti_velruse MUST BE THE LAST ONE in the list of includes. - # - # It's definitely a bad idea to employ a route named ''. - # But, in order to avoid this, we would have to change openid-selector too much :( - # ... which is outside of our requirements for this demo. - #################################################################################### - config.add_static_view(name='', path='kotti_velruse:openid-selector') - + try: + import openid_selector + log.info('openid_selector loaded successfully') + config.add_static_view(name='js', path='openid_selector:/js') + config.add_static_view(name='css', path='openid_selector:/css') + config.add_static_view(name='images', path='openid_selector:/images') + except Exception as e: + log.error(e) + raise e + log.info('kotti_velruse views are configured.') + def login(request): settings = request.registry.settings - project = settings['kotti.site_title'] - return { - 'project' : project, - 'login_url': request.route_url('login_'), - } + try: + #TODO:: before_kotti_velruse_loggedin(request) + return { + 'project' : settings['kotti.site_title'], + 'login_url': request.route_url('login_'), + } + except Exception as e: + log.error(e.message) + raise HTTPNotFound(e.message).exception def login_(request): - #################################################################################### - # Let's clarify the difference between "provider" and "method": - # - # * Conceptually, methods can be understood pretty much like protocols or transports. - # So, methods would be for example: OpenID, OAuth2, CAS, LDAP. - # * A provider is simply an entity, like Verisign, Google, Yahoo, Launchpad and - # hundreds of other entities which employ popular methods like OpenID and OAuth2. - # * In particular, certain entities implement their own methods (or protocols) or - # they eventually offer several authentication methods. For this reason, there are - # specific methods for "yahoo", "tweeter", "google_hybrid", "google_oauth2", etc. - # - # For the SAKE OF SIMPLICITY we arbitrarity consider providers and methods simply - # as entities in this function in particular. - #################################################################################### - provider=request.params['method'] + ###################################################################################### + # # + # Let's clarify the difference between "provider" and "method" in this function: # + # # + # * Conceptually, [authentication] methods can be understood pretty much like # + # protocols or transports. So, methods would be for example: OpenID, OAuth2 and # + # other authentication protocols supported by Velruse. # + # # + # * A provider is simply an entity, like Google, Yahoo, Twitter, Facebook, Verisign, # + # Github, Launchpad and hundreds of other entities which employ authentication # + # methods like OpenID, OAuth2 and others supported by Velruse. # + # # + # * In particular, certain entities implement their own authentication methods or # + # they eventually offer several authentication methods. For this reason, there are # + # specific methods for "yahoo", "tweeter", "google_hybrid", "google_oauth2", etc. # + # # + ###################################################################################### + + provider = request.params['provider'] + method = request.params['method'] settings = request.registry.settings - if not provider in find_providers(settings): - raise HTTPNotFound('Provider "{}" is not configured'.format(provider)).exception + if not method in find_providers(settings): + raise HTTPNotFound('Provider/method {}/{} is not configured'.format(provider, method)).exception - velruse_url = login_url(request, provider) + velruse_url = login_url(request, method) payload = dict(request.params) - if 'yahoo' == provider: payload['oauth'] = 'true' - if 'facebook' == provider: payload['scope'] = 'email,publish_stream,read_stream,create_event,offline_access' - if 'openid' == provider: payload['use_popup'] = 'false' + if 'yahoo' == method: payload['oauth'] = 'true' + if 'openid' == method: payload['use_popup'] = 'false' payload['format'] = 'json' + del payload['provider'] + del payload['method'] redirect = Request.blank(velruse_url, POST=payload) try: response = request.invoke_subrequest( redirect ) return response - except: - message = 'Provider "{}" is probably misconfigured'.format(provider) + except Exception as e: + log.error(e.message) + message = _(u'Provider/method: {}/{} :: {}').format(provider, method, e.message) raise HTTPNotFound(message).exception + def logged_in(request): token = request.params['token'] storage = request.registry.velruse_store try: - return storage.retrieve(token) - except KeyError: - message = 'invalid token "{}"'.format(token) - log.error(message) - return { 'error' : message } + json = storage.retrieve(token) + return json + except Exception as e: + log.error(e.message) + raise HTTPNotFound(e.message).exception def logout(request): from pyramid.security import forget - request.session.invalidate() - request.session.flash('Session logoff.') - headers = forget(request) - return HTTPFound(location=request.route_url('login'), headers=headers) + try: + request.session.invalidate() + request.session.flash( _(u'Session logged out.') ) + headers = forget(request) + return HTTPFound(location=request.application_url, headers=headers) + except Exception as e: + log.error(e.message) + raise HTTPNotFound(e.message).exception diff --git a/examples/kotti_velruse/run-server.sh b/examples/kotti_velruse/run-server.sh index ebafd27..f6bb8b6 100755 --- a/examples/kotti_velruse/run-server.sh +++ b/examples/kotti_velruse/run-server.sh @@ -1,20 +1,28 @@ #/bin/bash + +# give a little push to Kotti installation +pip install -r https://raw.github.com/Kotti/Kotti/0.9.2/requirements.txt $PIP_OPTIONS + +# installs Kotti and kotti_velruse +python setup.py develop + +# uninstall velruse cos rgomes-velruse replaces it for the time being +pip uninstall velruse << EOF +y +EOF + # make sure proxy settings are ignored `env | fgrep -i _proxy | cut -d= -f1 | xargs echo unset` -if [ ! -d kotti_velruse/openid-selector ] ;then - if [ -d ~/sources/frgomes/openid-selector/master/openid-selector ] ;then - ln -s ~/sources/frgomes/openid-selector/master/openid-selector kotti_velruse/openid-selector - else - which git - if [ $? -ne 0 ] ; then - sudo apt-get install git -y - fi - pushd kotti_velruse - git clone https://github.com/frgomes/openid-selector.git - popd - fi -fi - +# start server +echo . +echo . +echo '*************************************************' +echo '* *' +echo '* Starting the server... *' +echo '* *' +echo '* Please visit context /login when it is ready. *' +echo '* *' +echo '*************************************************' pserve development.ini --reload diff --git a/examples/kotti_velruse/setup.py b/examples/kotti_velruse/setup.py index 0571dde..1e07e96 100644 --- a/examples/kotti_velruse/setup.py +++ b/examples/kotti_velruse/setup.py @@ -8,6 +8,7 @@ requires = [ 'Kotti', + 'kotti_velruse', ] setup(name='kotti_velruse', @@ -29,7 +30,4 @@ include_package_data=True, zip_safe=False, install_requires=requires, - entry_points=""" - # -*- Entry points: -*- - """, )