Skip to content

Commit

Permalink
Merge pull request #557 from sandialabs/custom-auth-branch
Browse files Browse the repository at this point in the history
Custom auth branch to fix #549
  • Loading branch information
Matt authored and Matt committed Jan 7, 2016
2 parents 689bb60 + 70697d9 commit 7584576
Show file tree
Hide file tree
Showing 9 changed files with 430 additions and 121 deletions.
10 changes: 9 additions & 1 deletion packages/slycat/web/server/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def abspath(path):
dispatcher.connect("delete-upload", "/uploads/:uid", slycat.web.server.handlers.delete_upload, conditions={"method" : ["DELETE"]})

dispatcher.connect("logout", "/logout", slycat.web.server.handlers.logout, conditions={"method" : ["DELETE"]})
dispatcher.connect("login", "/login/user/:un/password/:pw", slycat.web.server.handlers.login, conditions={"method" : ["POST"]})
dispatcher.connect("login", "/login", slycat.web.server.handlers.login, conditions={"method" : ["POST"]})

def log_configuration(tree, indent=""):
for key, value in sorted(tree.items()):
Expand Down Expand Up @@ -215,6 +215,14 @@ def log_configuration(tree, indent=""):
"tools.staticfile.filename": abspath("templates/slycat-logout.html"),
"tools.staticfile.on": True,
}
configuration["/login"] = {
"tools.expires.force": True,
"tools.expires.on": True,
"tools.expires.secs": 3600,
"tools.%s.on" % authentication : False,
"tools.staticdir.dir": abspath("slycat-login"),
"tools.staticdir.on": True,
}
configuration["/resources/global/slycat-logo-navbar.png"] = {
"tools.expires.force": True,
"tools.expires.on": True,
Expand Down
94 changes: 87 additions & 7 deletions packages/slycat/web/server/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
import threading
import time
import uuid
import functools
from cherrypy._cpcompat import base64_decode
import datetime

def css_bundle():
with css_bundle._lock:
Expand Down Expand Up @@ -663,26 +666,103 @@ def delete_upload(uid):
slycat.web.server.upload.delete_session(uid)
cherrypy.response.status = "204 Upload session deleted."


@cherrypy.tools.json_in(on = True)
@cherrypy.tools.json_out(on = True)
def login(un, pw):
cherrypy.response.status = "404 no auth found" + un + pw
def login():
"""
Takes the post object under cherrypy.request.json with the users name and password
and determins with the user can be authenticated with slycat
:return: authentication status
"""

if "slycatauth" in cherrypy.request.cookie:
try:
sid = cherrypy.request.cookie["slycatauth"].value
couchdb = slycat.web.server.database.couchdb.connect()
session = couchdb.get("session", sid)
if session is not None:
couchdb.delete(session)
except:
pass

# try and decode the username and password
try:
user_name = base64_decode(cherrypy.request.json["user_name"])
password = base64_decode(cherrypy.request.json["password"])
except:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 400")
raise cherrypy.HTTPError(400)
realm = None

# Get the client ip, which might be forwarded by a proxy.
remote_ip = cherrypy.request.headers.get("x-forwarded-for") if "x-forwarded-for" in cherrypy.request.headers else cherrypy.request.rem

# see if we have a registered password checking function from our config
if login.password_check is None:
if "password-check" not in cherrypy.request.app.config["slycat-web-server"]:
raise cherrypy.HTTPError("500 No password check configured.")
plugin = cherrypy.request.app.config["slycat-web-server"]["password-check"]["plugin"]
args = cherrypy.request.app.config["slycat-web-server"]["password-check"].get("args", [])
kwargs = cherrypy.request.app.config["slycat-web-server"]["password-check"].get("kwargs", {})
if plugin not in slycat.web.server.plugin.manager.password_checks.keys():
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 no password check plugin found.")
raise cherrypy.HTTPError("500 No password check plugin found.")
login.password_check = functools.partial(slycat.web.server.plugin.manager.password_checks[plugin], *args, **kwargs)

# time to test username and password
success, groups = login.password_check(realm, user_name, password)

if success:
# Successful authentication, create a session and return.
cherrypy.log.error("%s@%s: Password check succeeded." % (user_name, remote_ip))

sid = uuid.uuid4().hex
session = {"created": datetime.datetime.utcnow(), "creator": user_name}
database = slycat.web.server.database.couchdb.connect()
database.save({"_id": sid, "type": "session", "created": session["created"].isoformat(), "creator": session["creator"], 'groups': groups, 'ip': remote_ip})

login.sessions[sid] = session

cherrypy.response.cookie["slycatauth"] = sid
cherrypy.response.cookie["slycatauth"]["path"] = "/"
cherrypy.response.cookie["slycatauth"]["secure"] = 1
cherrypy.response.cookie["slycatauth"]["httponly"] = 1
cherrypy.response.status = "200 OK"
cherrypy.request.login = user_name#TODO:might be able to delete this
else:
cherrypy.response.status = "404 no auth found!!!"
return {'session': 'stuff','sid' : sid, 'user_name': user_name, 'password': password, 'success': success, 'groups': groups, 'ip': remote_ip}


login.password_check = None
login.sessions = {}
login.session_cleanup = None

def logout():
# See if the client has a valid session.
"""
See if the client has a valid session.
If so delete it
:return: the status of the request
"""
try:
if "slycatauth" in cherrypy.request.cookie:
sid = cherrypy.request.cookie["slycatauth"].value

# expire the old cookie
cherrypy.response.cookie["slycatauth"] = sid
cherrypy.response.cookie["slycatauth"]['expires'] = 0

couchdb = slycat.web.server.database.couchdb.connect()
session = couchdb.get("session", sid)
if session is not None:
cherrypy.response.status = "204 session deleted." + json.dumps(session) + str(couchdb.delete(session))
cherrypy.response.status = "200 session deleted." + str(couchdb.delete(session))
else:
cherrypy.response.status = "204 session not deleted." + json.dumps(session) + sid + str(session.get("_id") is sid) + ":::::" + session.get("_id") + ":::::" + sid
cherrypy.response.status = "400 Bad Request no session to delete."
else:
cherrypy.response.status = "401 no auth found"
cherrypy.response.status = "403 Forbidden"
except Exception as e:
raise
raise cherrypy.HTTPError("400 Bad Request")

@cherrypy.tools.json_in(on = True)
def put_model_inputs(mid):
Expand Down
42 changes: 34 additions & 8 deletions web-server/plugins/slycat-custom-auth/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,33 @@ <h1 class="title">Login</h1>
<label for="Password">Password</label>
<div class="bar"></div>
</div>
<div class="button-container">
<button class="btn btn-default" id="go">GO</button>
</div>
</form>
<div class="button-container">
<button class="btn btn-default" id="go">GO</button>
</div>
<div class="button-container">
<button class="btn btn-default" id="logout">LOGOUT</button>
</div>
</div>
</div>
</div>
<!--div class="form-group">
<label for="slycat-login-username" class="col-sm-2 control-label">Username</label>
<div class="col-sm-10">
<input id="slycat-login-username" class="form-control" type="text" data-bind="enable:enable, textInput:username"></input>
</div>
</div>
<div class="form-group">
<label for="slycat-login-password" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input id="slycat-login-password" class="form-control" type="password" data-bind="enable:enable, textInput:password"></input>
</div>
</div>
<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<div class="alert fade" role="alert" data-bind="css:status_classes,text:status"></div>
</div>
</div-->

<script type="text/javascript">
require(["slycat-server-root", "jquery", "URI"], function(server_root, $, URI)
Expand All @@ -33,21 +51,30 @@ <h1 class="title">Login</h1>
//TODO: add post call for username and password
console.log("calling webservice with")
console.log("login " + user_name + " " + password);
console.log(server_root + "login/user/" + user_name + "/password/" + password)
console.log(server_root + "login/")
var sendInfo = JSON.stringify(
{
"user_name": user_name,
"password": password
}
);

$.ajax(
{
dataType: "json",
contentType: "application/json",
type: "POST",
url: URI(server_root + "login/user/" + user_name + "/password/" + password),
url: URI(server_root + "login"),
success: function(result)
{
window.alert("success " + result);
console.log("success " + result);
},
error: function(request, status, reason_phrase)
{
console.log("error " + request + status + reason_phrase);
window.alert("error request:" + request.responseJSON +" status: "+ status + " reason: " + reason_phrase);
console.log("error request:" + request.responseJSON +" status: "+ status + " reason: " + reason_phrase);
},
data: sendInfo
});

console.log("done")
Expand All @@ -74,4 +101,3 @@ <h1 class="title">Login</h1>
document.getElementById("logout").addEventListener("click", logout, false);
});
</script>
</div>
149 changes: 44 additions & 105 deletions web-server/plugins/slycat-standard-authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,114 +34,53 @@ def authenticate(realm, rules=None):
session = None
try:
session = couchdb.get("session", sid)
except:
pass
if sid in authenticate.sessions:
started = authenticate.sessions[sid]["created"]
if datetime.datetime.utcnow() - started > cherrypy.request.app.config["slycat"]["session-timeout"]:
del authenticate.sessions[sid]
elif session is None:
cherrypy.log.error("@%s: deleting local session." % (remote_ip))
del authenticate.sessions[sid]
cherrypy.response.headers["www-authenticate"] = "Basic realm=\"%s\"" % realm
raise cherrypy.HTTPError(401, "Authentication required.")
else:
# Ensure that the user is logged correctly ...
cherrypy.request.login = authenticate.sessions[sid]["creator"]
return
else:
# Expired or forged cookie
cherrypy.log.error("@%s: expired/unknown session." % (remote_ip))

# If the client hasn't authenticated, tell them to do so.
authorization = cherrypy.request.headers.get("authorization")
if authorization is None:
cherrypy.response.headers["www-authenticate"] = "Basic realm=\"%s\"" % realm
raise cherrypy.HTTPError(401, "Authentication required.")

# Parse the client's authentication response.
try:
scheme, params = authorization.split(" ", 1)
except:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 400")
raise cherrypy.HTTPError(400)
if scheme.lower() != "basic":
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 400")
raise cherrypy.HTTPError(400)
try:
username, password = base64_decode(params).split(":", 1)
except:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 400")
raise cherrypy.HTTPError(400)

cherrypy.log.error("%s@%s: Checking password." % (username, remote_ip))

if authenticate.password_check is None:
if "password-check" not in cherrypy.request.app.config["slycat-web-server"]:
raise cherrypy.HTTPError("500 No password check configured.")
plugin = cherrypy.request.app.config["slycat-web-server"]["password-check"]["plugin"]
args = cherrypy.request.app.config["slycat-web-server"]["password-check"].get("args", [])
kwargs = cherrypy.request.app.config["slycat-web-server"]["password-check"].get("kwargs", {})
if plugin not in slycat.web.server.plugin.manager.password_checks.keys():
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 no password check plugin found.")
raise cherrypy.HTTPError("500 No password check plugin found.")
authenticate.password_check = functools.partial(slycat.web.server.plugin.manager.password_checks[plugin], *args, **kwargs)

success, groups = authenticate.password_check(realm, username, password)
if success:
# Apply (optional) authentication rules.
if rules is not None:
deny = None
for operation, category, members in rules:
if operation not in ["allow", "deny"]:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 unknown operation: %s." % operation)
raise cherrypy.HTTPError("500 Unknown operation: %s." % operation)
if category not in ["users", "groups"]:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 unknown category: %s." % category)
raise cherrypy.HTTPError("500 Unknown category: %s." % category)

operation_default = True if operation == "allow" else False
operation_deny = False if operation == "allow" else True

if deny is None:
deny = operation_default
if category == "users":
if username in members:
deny = operation_deny
elif category == "groups":
for group in groups:
if group in members:
started = session["created"]
user_name = session["creator"]
groups = session["groups"]
if datetime.datetime.utcnow() - datetime.datetime.strptime(unicode(started), '%Y-%m-%dT%H:%M:%S.%f') > cherrypy.request.app.config["slycat"]["session-timeout"]:
couchdb.delete(session)
# expire the old cookie
cherrypy.response.cookie["slycatauth"] = sid
cherrypy.response.cookie["slycatauth"]['expires'] = 0
session = None
cherrypy.request.login = user_name
# Apply (optional) authentication rules.
if rules and user_name is not None:
deny = None
for operation, category, members in rules:
if operation not in ["allow", "deny"]:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 unknown operation: %s." % operation)
raise cherrypy.HTTPError("500 Unknown operation: %s." % operation)
if category not in ["users", "groups"]:
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 500 unknown category: %s." % category)
raise cherrypy.HTTPError("500 Unknown category: %s." % category)

operation_default = True if operation == "allow" else False
operation_deny = False if operation == "allow" else True

if deny is None:
deny = operation_default
if category == "users":
if user_name in members:
deny = operation_deny
break
elif category == "groups":
for group in groups:
if group in members:
deny = operation_deny
break

if deny:
raise cherrypy.HTTPError("403 User denied by authentication rules.")
if deny:
raise cherrypy.HTTPError("403 User denied by authentication rules.")
except Exception as e:
cherrypy.log.error("@%s: could not get db session." % (e))

# Successful authentication, create a session and return.
cherrypy.log.error("%s@%s: Password check succeeded." % (username, remote_ip))

sid = uuid.uuid4().hex
session = {"created": datetime.datetime.utcnow(), "creator": username}
database = slycat.web.server.database.couchdb.connect()
database.save({"_id": sid, "type": "session", "created": session["created"].isoformat(), "creator": session["creator"]})

authenticate.sessions[sid] = session
# there was no session time to authenticate
if session is None:
raise cherrypy.HTTPRedirect("/login/slycat-login.html", 307)

cherrypy.response.cookie["slycatauth"] = sid
cherrypy.response.cookie["slycatauth"]["path"] = "/"
cherrypy.response.cookie["slycatauth"]["secure"] = 1
cherrypy.response.cookie["slycatauth"]["httponly"] = 1
cherrypy.request.login = username
return # successful authentication

# Authentication failed, tell the client to try again.
cherrypy.log.error("%s@%s: Password check failed." % (username, remote_ip))
cherrypy.response.headers["www-authenticate"] = "Basic realm=\"%s\"" % realm
slycat.email.send_error("slycat-standard-authentication.py authenticate", "cherrypy.HTTPError 401 authentication failed for user %s." % username)
raise cherrypy.HTTPError(401, "Authentication required.")

authenticate.password_check = None
authenticate.sessions = {}
authenticate.session_cleanup = None
# Successful authentication, create a session and return.
#return
else:
raise cherrypy.HTTPRedirect("/login/slycat-login.html", 307)

context.register_tool("slycat-standard-authentication", "on_start_resource", authenticate)
Loading

0 comments on commit 7584576

Please sign in to comment.