Permalink
Browse files

...

  • Loading branch information...
dagnelies committed Jul 22, 2016
1 parent 42b428d commit ebc47b027f2abd6d456d316cf0a7aa90af3262e6
Showing with 58 additions and 773 deletions.
  1. +2 −1 .gitignore
  2. +28 −5 README.md
  3. +22 −7 canister.py
  4. +0 −1 examples/basic.py
  5. +0 −312 examples/logs/log
  6. +0 −61 examples/logs/log.2016-07-02
  7. +0 −384 examples/logs/log.2016-07-03
  8. +6 −2 setup.py
View
@@ -1,3 +1,4 @@
dist
__pycache__
canister.egg-info
canister.egg-info
examples/logs
View
@@ -9,6 +9,8 @@ Canister is a simple plugin for bottle, providing:
- authentication through basic auth or bearer token (OAuth2)
- CORS for cross-domain REST APIs
#### *Note: the `examples` directory is outdated.*
### Usage
```
@@ -38,10 +40,8 @@ log_level = INFO
# Log older than that will be deleted
log_days = 30
# (not yet implemented) how long the session data will still be available after the last access
session_expiration = 30d
# (not yet implemented) the interval to check for obsolete sessions
session_check_interval = 1h
# how long the session data will still be available after the last access, in seconds
session_timout = 3600
# applies CORS to responses, write * to allow AJAX requests from anywhere
#CORS = *
@@ -58,6 +58,7 @@ auth_basic_password = my-secret
# Auth using JWT (for OAuth2)
auth_client_id = ABC
# accepted encodings are "clear", "base64std" or "base64url"
auth_jwt_encoding = base64url
auth_jwt_secret = my-secret
```
@@ -101,4 +102,26 @@ This can be seen in the logs `[149.172.44.162-VJ8zq5]` in order to be able to ea
### Authentication
### CORS
### CORS
### Security
One of the common security flaws of web apps is Cross Site Request Forgery (https://en.wikipedia.org/wiki/Cross-site_request_forgery)
Either:
- provide auth-creditentials
Or:
- provide a HTTP-Only cookie containing your session ID (to prove your're authenticated and prevent XSS, done by the server)
- and provide a session token as parameter or in a "X-Csrf-Token" header (to prove it comes from your Browser and prevent CSFR, must be done client side through javascript)
---
Provide HTTP-Only cookie + check Referer Header.
How does this prevent CSRF?
- if the request doesn't come from the browser, the cookie/session-id is unknown
- if it comes from the browser, we can ensure it comes from the site itself
...the only issue is in case the header is removed (because of a privacy proxy or plugin, but I think it's pretty seldom)
...the issue is that any other method requires client side stuff in the page (through hidden fields, request parameters or setting specific headers through javascript)
View
@@ -20,6 +20,7 @@
import hashlib
import inspect
import time
import math
class TimedDict(dict):
@@ -59,7 +60,9 @@ def prune(self, age):
for (k, (t,val)) in self._items.items():
if now - t < age:
survivors[k] = (t,val)
pruned = len(self._items) - len(survivors)
self._items = survivors
return pruned
@@ -107,7 +110,7 @@ def validate(token):
def _buildAuthJWT(config):
client_id = config.get('canister.auth_jwt_client_id', None)
secret = config.get('canister.auth_jwt_secret', None)
encoding = config.get('canister.auth_jwt_encoding', 'clear').lower() # clear, base64, or base64url
encoding = config.get('canister.auth_jwt_encoding', 'clear').lower() # clear, base64std, or base64url
if not client_id or not secret:
return None
@@ -116,7 +119,11 @@ def _buildAuthJWT(config):
secret = base64.standard_b64decode(secret)
elif encoding == 'base64url': # with - and _
secret = base64.urlsafe_b64decode(secret)
elif encoding == 'clear':
pass
else:
raise Exception('Invalid auth_jwt_encoding in config: "%s" (should be "clear", "base64std" or "base64url")' % encoding)
def validate(token):
profile = jwt.decode(token, secret, audience=client_id)
return profile
@@ -125,17 +132,24 @@ def validate(token):
class SessionCache:
def __init__(self, interval=60, max_age=3600, log=None):
def __init__(self, timeout=3600):
self._lock = threading.Lock()
self._cache = TimedDict()
log = logging.getLogger('canister')
if timeout <= 0:
log.warn('Sessions kept indefinitely! (session timeout is <= 0)')
return
interval = int(math.sqrt(timeout))
log.info('Session timeout is %d seconds. Checking for expired sessions every %d seconds. ' % (timeout, interval))
def prune():
while True:
time.sleep(interval)
with self._lock:
n = self._cache.prune(max_age)
if log:
log.debug('%d expired sessions pruned' % n)
n = self._cache.prune(timeout)
log.debug('%d expired sessions pruned' % n)
cleaner = threading.Thread(name="SessionCleaner", target=prune)
cleaner.deamon=True
@@ -196,7 +210,8 @@ def setup(self, app):
self.log = log
app.log = log
self.sessions = SessionCache()
timeout = int(config.get('canister.session_timeout', '3600'))
self.sessions = SessionCache(timeout=timeout)
self.session_secret = base64.b64encode(os.urandom(30)).decode('ascii')
self.auth_basic = _buildAuthBasic(config)
View
@@ -4,7 +4,6 @@
import canister
app = bottle.Bottle()
app.config.load_config('example.config')
app.install(canister.Canister())
Oops, something went wrong.

0 comments on commit ebc47b0

Please sign in to comment.