Commit 6fc932f

root <root@tape.intranet.ltsnet.net>
2013-12-17 12:22:34
auth add
1 parent 0824b00
static/ffu.css
@@ -1,5 +1,6 @@
 body {
   padding-top: 60px;
+  padding-bottom: 40px;
 }
 #form-upload {
   padding: 15px;
@@ -13,8 +14,43 @@ body {
   line-height: 55px;
 }
 .top-padding { 
-  margin-top:20px; 
+  margin-top:10px; 
 }
 .bot-padding { 
-  margin-bottom:20px; 
+  margin-bottom:10px; 
+}
+
+.form-signin {
+  max-width: 330px;
+  padding: 15px;
+  margin: 0 auto;
+}
+.form-signin .form-signin-heading,
+.form-signin .checkbox {
+  margin-bottom: 10px;
+}
+.form-signin .checkbox {
+  font-weight: normal;
+}
+.form-signin .form-control {
+  position: relative;
+  font-size: 16px;
+  height: auto;
+  padding: 10px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+.form-signin .form-control:focus {
+  z-index: 2;
+}
+.form-signin input[type="text"] {
+  margin-bottom: -1px;
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+}
+.form-signin input[type="password"] {
+  margin-bottom: 10px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
 }
static/signin.css
@@ -1,39 +0,0 @@
-body {
-  padding-top: 60px;
-  padding-bottom: 40px;
-}
-
-.form-signin {
-  max-width: 330px;
-  padding: 15px;
-  margin: 0 auto;
-}
-.form-signin .form-signin-heading,
-.form-signin .checkbox {
-  margin-bottom: 10px;
-}
-.form-signin .checkbox {
-  font-weight: normal;
-}
-.form-signin .form-control {
-  position: relative;
-  font-size: 16px;
-  height: auto;
-  padding: 10px;
-  -webkit-box-sizing: border-box;
-     -moz-box-sizing: border-box;
-          box-sizing: border-box;
-}
-.form-signin .form-control:focus {
-  z-index: 2;
-}
-.form-signin input[type="text"] {
-  margin-bottom: -1px;
-  border-bottom-left-radius: 0;
-  border-bottom-right-radius: 0;
-}
-.form-signin input[type="password"] {
-  margin-bottom: 10px;
-  border-top-left-radius: 0;
-  border-top-right-radius: 0;
-}
templates/admin.html
@@ -0,0 +1,11 @@
+{% extends "base-nav.html" %}
+
+{% block css %}
+<link rel="stylesheet" href="{{ static_url("ffu.css") }}">
+{% end %}
+
+{% block content %}
+<div class="container">
+  Admin'y things here
+</div><!-- /.container -->
+{% end %}
templates/base-nav.html
@@ -0,0 +1,34 @@
+{%extends "base.html" %}
+{% block nav %}
+<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+  <div class="container">
+    <div class="navbar-header">
+      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+        <span class="sr-only">Toggle navigation</span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </button>
+      <a class="navbar-brand" href="/">FFU</a>
+    </div>
+    <div class="collapse navbar-collapse">
+      <ul class="nav navbar-nav">
+        <li class="active"><a href="/">Upload</a></li>
+        {% if admin is not None %}
+        <li><a href="/admin">Admin</a></li>
+        {% end %}
+      </ul>
+      {% if current_user is not None %}
+      <ul class="nav navbar-nav navbar-right">
+        <li>
+        <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ current_user }}<b class="caret"></b></a>
+        <ul class="dropdown-menu">
+          <li><a href="/logout">Logout</a></li>
+        </ul>
+        </li>
+      </ul>
+      {% end %}
+    </div><!--/.nav-collapse -->
+  </div>
+</div>
+{% end %}
templates/base.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="description" content="">
+  <meta name="author" content="">
+  <link rel="shortcut icon" href="{{ static_url("images/favicon.ico")}}">
+  <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
+  {% block css %}{% end %}
+</head>
+
+<body>
+  {% block nav %}{% end %}
+  {% block content %}{% end %}
+
+  <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
+  <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
+  {% block js %}{% end %}
+</body>
templates/index.html
@@ -0,0 +1,33 @@
+{% extends "base-nav.html" %}
+
+{% block css %}
+<link rel="stylesheet" href="{{ static_url("ffu.css") }}">
+{% end %}
+
+{% block content %}
+<div class="container">
+  <form id="form-upload" enctype="multipart/form-data" action="" method="post">
+    <input type="file" id="fileselect" name="fileselect" multiple="multiple" style="display:none"/>
+    <div class="row">
+      <div class="col-md-8 col-sm-8 bot-padding">
+        <div id="upload-zone" class="well upload-zone">
+          <p id="upload-text" class="lead text-center text-muted">Drop files or Click here</p>
+        </div>
+      </div>
+      <div class="col-md-4 col-sm-4 bot-padding">
+        <button class="btn btn-lg btn-default btn-block" type="submit">Upload</button>
+        <button class="btn btn-lg btn-default btn-block" type="reset">Clear</button>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-xs-12">
+        <ul id="filelist" class="list-group"></ul>
+      </div>
+    </div>
+  </form>
+</div><!-- /.container -->
+{% end %}
+
+{% block js %}
+<script src="{{ static_url("ffu.js") }}"></script>
+{% end %}
templates/login.html
@@ -0,0 +1,21 @@
+{% extends "base-nav.html" %}
+
+{% block css %}
+<link rel="stylesheet" href="{{ static_url("ffu.css") }}">
+{% end %}
+
+{% block content %}
+<div class="container">
+  <form class="form-signin" role="form" action="" method="post">
+    <input type="text" class="form-control" name="username" placeholder="Username" required autofocus>
+    <input type="password" class="form-control" name="password" placeholder="Password" required>
+    <!--<label class="checkbox">
+      <input type="checkbox" value="remember-me"> Remember me
+    </label>-->
+    {% if errormsg is not None %}
+    <div class="alert-warning text-center bot-padding">{{ errormsg }}</div>
+    {% end %}
+    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+  </form>
+</div> <!-- /container -->
+{% end %}
.gitignore
@@ -0,0 +1,1 @@
+config.py
ffu.py
@@ -7,9 +7,12 @@ import base64
 from StringIO import StringIO
 import os
 
+from login import login
+
 class BaseHandler(tornado.web.RequestHandler):
   def get_login_url(self):
     return u"/login"
+
   def get_current_user(self):
     user_json = self.get_secure_cookie("user")
     if user_json:
@@ -17,62 +20,100 @@ class BaseHandler(tornado.web.RequestHandler):
     else:
       return None
 
+  def get_admin_status(self):
+    admin_json = self.get_secure_cookie("admin")
+    if admin_json:
+      return tornado.escape.json_decode(admin_json)
+    else:
+      return None
+
+  def render(self, template, **kwargs):
+    if hasattr(self, 'errormsg'):
+      kwargs['errormsg'] = self.errormsg
+    else:
+      kwargs['errormsg'] = None
+    kwargs['admin'] = self.get_admin_status()
+    super(BaseHandler, self).render(template, **kwargs)
+
 class LoginHandler(BaseHandler):
   def get(self):
-    self.render('login.html', next=self.get_argument("next","/"))
+    self.render('login.html') 
+  
   def post(self):
-    print self
-    print vars(self)
     username = self.get_argument("username", "")
     password = self.get_argument("password", "")
-    print "login: ",username," ",password 
-    auth = False
-    if username == "user" and password == "pass":
-      auth = True
-    if auth:
+    status, error =  login(username, password)
+    status = ((username=="user" or username=="userb") and password=="pass")
+    if status:
+      adminstatus = error #Yes, ghetto
+      adminstatus = (username=="userb")
       self.set_current_user(username)
-      self.redirect(self.get_argument("next","/"))
+      self.set_admin_status(adminstatus)
+      self.redirect(self.get_argument("next",u"/"))
     else:
-      error_msg = u"?error=" + tornado.escape.url_escape("Login Failed")
-      self.redirect(u"/login" + error_msg)
+      self.errormsg = error
+      self.render("login.html", errormsg=self.errormsg)
+
   def set_current_user(self, user):
     if user:
       self.set_secure_cookie("user", tornado.escape.json_encode(user))
     else:
       self.clear_cookie("user")
 
+  def set_admin_status(self, admin):
+    if admin:
+      self.set_secure_cookie("admin", tornado.escape.json_encode(admin))
+    else:
+      self.clear_cookie("admin")
+
 class LogoutHandler(BaseHandler):
   def get(self):
     self.clear_cookie("user")
+    self.clear_cookie("admin")
     self.redirect(u"/login")
 
-class UploadHandler(tornado.web.RequestHandler):
+class UploadHandler(BaseHandler):
+  @tornado.web.authenticated
   def get(self):
     self.render("index.html")
+    #username = self.current_user, admin = self.get_admin_status())
   
+  @tornado.web.authenticated
   def post(self):
     form_name = 'fileselect'
     upload_dir = 'uploads/'
     if self.request.files:
-      
+     
+      # replace with call to actual validation and tar'ing 
       tar = tarfile.open(upload_dir + str(uuid.uuid4()) + ".tar.gz", "w:gz")
       fileinfo = self.request.files.itervalues()
       for f in fileinfo:
-        #print f[0]['filename'], f[0]['content_type'], len(f[0]['body'])
         f_info = tarfile.TarInfo(name=f[0]['filename'])
         f_info.size = len(f[0]['body'])
         tar.addfile(f_info, StringIO(f[0]['body']))
       tar.close()
 
+class AdminHandler(BaseHandler):
+  @tornado.web.authenticated
+  def get(self):
+    if self.get_admin_status():
+      self.render("admin.html")
+                  #username = self.current_user, admin = self.get_admin_status())
+    else:
+      self.redirect(u"/")
+
 handlers = [
   (r"/", UploadHandler),
+  (r"/admin", AdminHandler),
   (r"/login", LoginHandler),
   (r"/logout", LogoutHandler),
 ]  
 
 settings = {
-  "static_path": os.path.join(os.path.dirname(__file__),"static"),
+  # set cookie_secret to hard coded string for production, currently breaks existing cookies on restart
   "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes),
+  "static_path": os.path.join(os.path.dirname(__file__),"static"),
+  "template_path": os.path.join(os.path.dirname(__file__),"templates"),
 }
 
 application = tornado.web.Application(handlers, **settings )
index.html
@@ -1,59 +0,0 @@
-<!DOCTYPE html>
-<head>
-  <meta charset="utf-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta name="description" content="">
-  <meta name="author" content="">
-  <link rel="shortcut icon" href="{{ static_url("images/favicon.ico")}}">
-  <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
-  <link rel="stylesheet" href="{{ static_url("ffu.css") }}">
-</head>
-
-<body>
-  <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
-    <div class="container">
-      <div class="navbar-header">
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
-          <span class="sr-only">Toggle navigation</span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </button>
-        <a class="navbar-brand" href="#">FFU</a>
-      </div>
-      <div class="collapse navbar-collapse">
-        <ul class="nav navbar-nav">
-          <li class="active"><a href="#">Home</a></li>
-          <li><a href="#about">Admin</a></li>
-        </ul>
-      </div><!--/.nav-collapse -->
-    </div>
-  </div>
-
-  <div class="container">
-    <form id="form-upload" enctype="multipart/form-data" action="" method="post">
-      <input type="file" id="fileselect" name="fileselect" multiple="multiple" style="display:none"/>
-      <div class="row">
-        <div class="col-md-8 col-sm-8 bot-padding">
-          <div id="upload-zone" class="well upload-zone">
-            <p id="upload-text" class="lead text-center text-muted">Drop files or Click here</p>
-          </div>
-        </div>
-        <div class="col-md-4 col-sm-4 bot-padding">
-          <button class="btn btn-lg btn-default btn-block" type="submit">Upload</button>
-          <button class="btn btn-lg btn-default btn-block" type="reset">Clear</button>
-        </div>
-      </div>
-      <div class="row">
-      <div class="col-xs-12">
-      <ul id="filelist" class="list-group"></ul>
-      </div>
-      </div>
-    </form>
-  </div><!-- /.container -->
-
-  <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
-  <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
-  <script src="{{ static_url("ffu.js") }}"></script>
-</body>
login.html
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<head>
-  <meta charset="utf-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta name="description" content="">
-  <meta name="author" content="">
-  <link rel="shortcut icon" href="{{ static_url("images/favicon.ico")}}">
-  <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
-  <link rel="stylesheet" href="{{ static_url("signin.css") }}">
-</head>
-
-<body>
-  <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
-    <div class="container">
-      <div class="navbar-header">
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
-          <span class="sr-only">Toggle navigation</span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </button>
-        <a class="navbar-brand" href="#">FFU</a>
-      </div>
-      <div class="collapse navbar-collapse">
-        <ul class="nav navbar-nav">
-          <li class="active"><a href="#">Home</a></li>
-          <li><a href="#about">Admin</a></li>
-        </ul>
-      </div><!--/.nav-collapse -->
-    </div>
-  </div>
-
-  <div class="container">
-    <form class="form-signin" role="form" action="" method="post">
-      <input type="text" class="form-control" name="username" placeholder="Username" required autofocus>
-      <input type="password" class="form-control" name="password" placeholder="Password" required>
-      <!--<label class="checkbox">
-        <input type="checkbox" value="remember-me"> Remember me
-      </label>-->
-      <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
-    </form>
-  </div> <!-- /container -->
-  <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
-  <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
-  <script src="{{ static_url("ffu.js") }}"></script>
-</body>
login.py
@@ -0,0 +1,42 @@
+import ldap
+from config import LDAP_SERVER, LDAP_DOMAIN, LDAP_TRANSFER_GROUP, LDAP_ADMIN_GROUP, LDAP_BASE_DN
+ 
+def login(username, password):
+   """Verifies credentials for username and password.
+   Returns None on success or a string describing the error on failure
+   # Adapt to your needs
+   """
+   # fully qualified AD user name
+   LDAP_USERNAME = '%s@%s' % (username, LDAP_DOMAIN)
+   # your password
+   LDAP_PASSWORD = password
+   ldap_filter = 'userPrincipalName=%s@%s' % (username, LDAP_DOMAIN)
+   attrs = ['memberOf']
+   try:
+       # build a client
+       ldap_client = ldap.initialize(LDAP_SERVER)
+       # perform a synchronous bind
+       ldap_client.set_option(ldap.OPT_REFERRALS,0)
+       ldap_client.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD)
+   except ldap.INVALID_CREDENTIALS:
+       ldap_client.unbind()
+       return False, 'Wrong username or password'
+   except ldap.SERVER_DOWN:
+       return False, 'AD server not awailable'
+   # all is well so far
+   # get all user groups and store it in cerrypy session for future use
+   groups = str(ldap_client.search_s(LDAP_BASE_DN,
+                   ldap.SCOPE_SUBTREE, ldap_filter, attrs)[0][1]['memberOf'])
+   ldap_client.unbind()
+   if LDAP_ADMIN_GROUP in groups:
+       return True, True #Admin
+   elif LDAP_TRANSFER_GROUP in groups:
+       return True, False #Not admin
+   else:
+       return False, 'Not in transfer group'
+
+if __name__ == "__main__":
+    import getpass
+    username = raw_input("Username: ")
+    password = getpass.getpass("Password: ")
+    print login(username, password)
README.md
@@ -1,8 +1,6 @@
-TODO Auth
-TODO add clear button
-TODO move upload up with upload_zone
+TODO Auth -> better way to do error message
+TODO add clear button -> diable until needed
 TODO add X button area on list elements to remove each
 TODO (python) send success message 
 TODO file name collisions deduplication
-TODO Disable Updload / Clear when appropriate