Merge branch 'master' of github.com:open-cloud/xos
diff --git a/.gitignore b/.gitignore
index 9fbdd02..b03d344 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
-*orig
-*pyc
+*.orig
+*.pyc
+*.swp
 profile
 *.moved-aside
diff --git a/cloudlab-init.sh b/cloudlab-init.sh
new file mode 100755
index 0000000..45ad726
--- /dev/null
+++ b/cloudlab-init.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# This script assumes that XOS is running in a Docker container on the local machine,
+# using an image called 'xos'.
+
+# CHANGE THE FOLLOWING FOR YOUR CLOUDLAB INSTALLATION
+# URL for your XOS installation
+XOS="http://192.168.59.103:8000/"
+# The IP address of the OpenStack head node on CloudLab
+CTL_IP="128.104.222.18"
+# The DNS name of the OpenStack compute node, as shown by 'nova hypervisor-list'
+NODE="cp1.acb-qv7993.planetcloud-pg0.wisc.cloudlab.us"
+# The public key that you want to use to login to instances
+PUBKEY="ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArlgZWcRP75W2/e5bKG1FEeec1OJQuw9dGVyo3TdUgVu4F0/JgBsgR2BrTuQ+mzm+N47ZkSrYwLdAJGuvL7ECxc6aouQ6AtQ/biU1gsrfuPnnUBjfAGqlP/L77lYxpLAPglx/HCCBu53gLKVt8lRDyyGZaWnB7fGlnwrn5AMjcfXsz5Ia8W6oBmxy2fxDSR9SpTs5yAzfcj37mCBtOZBwdjb54B36WpFq9BwFrEXxbvxH4aU0WSneJagicZuCUXnTg2YSURBD0jBmTrYOVRfTZzNPyagOvuIhnnakOSGkGa8s4SrC5zymZsVPdQbp6icRsu6OjKZ83Y0oiTQ4rTaeUw== acb@cadenza.cs.princeton.edu"
+# END CHANGES
+
+AUTH="padmin@vicci.org:letmein"
+CONTAINER=$( docker ps|grep xos|awk '{print $(NF)}' )
+
+# Copy public key
+# BUG: Shouldn't have to set the 'enacted' field...
+http --auth $AUTH PATCH $XOS/xos/users/1/ public_key="$PUBKEY" enacted=$( date "+%Y-%m-%dT%T")
+
+# Fix /etc/hosts, necessary for OpenStack endpoints
+docker exec $CONTAINER bash -c "echo $CTL_IP ctl >> /etc/hosts"
+
+# Set up controller
+http --auth $AUTH POST $XOS/xos/controllers/ name=CloudLab deployment=$XOS/xos/deployments/1/ backend_type=OpenStack version=Juno auth_url="http://ctl:5000/v2.0" admin_user=admin admin_tenant=admin admin_password="N!ceD3m0"
+
+# Add controller to site
+http --auth $AUTH PATCH $XOS/xos/sitedeployments/1/ controller=$XOS/xos/controllers/1/
+
+# Add image
+http --auth $AUTH POST $XOS/xos/images/ name=trusty-server-multi-nic disk_format=QCOW2 container_format=BARE
+
+# Activate image
+http --auth $AUTH POST $XOS/xos/imagedeploymentses/ deployment=$XOS/xos/deployments/1/ image=$XOS/xos/images/1/
+
+# Add node
+http --auth $AUTH POST $XOS/xos/nodes/ name=$NODE site_deployment=$XOS/xos/sitedeployments/1/
+
+# Modify networktemplate/2
+# BUG: Shouldn't have to set the controller_kind field, it's invalid in the initial fixture
+http --auth $AUTH PATCH $XOS/xos/networktemplates/2/ shared_network_name=tun-data-net controller_kind=""
diff --git a/xos/core/admin.py b/xos/core/admin.py
index a83ec7d..82239bd 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -487,6 +487,14 @@
     def queryset(self, request):
         return Site.select_by_user(request.user)
 
+class SiteHostsNodesInline(SiteInline):
+    def queryset(self, request):
+        return Site.select_by_user(request.user).filter(hosts_nodes=True)
+
+class SiteHostsUsersInline(SiteInline):
+    def queryset(self, request):
+        return Site.select_by_user(request.user).filter(hosts_users=True)        
+
 class UserInline(XOSTabularInline):
     model = User
     fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
@@ -916,7 +924,7 @@
 
 class SiteAdmin(XOSBaseAdmin):
     #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
-    fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
+    fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'login_base', 'location', 'is_public', 'hosts_nodes', 'hosts_users']
     fieldsets = [
         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
@@ -925,7 +933,7 @@
     readonly_fields = ['backend_status_text']
 
     #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
-    user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
+    user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'hosts_nodes', 'hosts_users']
 
     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
     list_display_links = ('backend_status_icon', 'name', )
@@ -1126,7 +1134,7 @@
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
-            kwargs['queryset'] = Site.select_by_user(request.user)
+            kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_users=True)
             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
 
         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@@ -1239,6 +1247,9 @@
 
     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
 
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == 'site':
+            kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_nodes=True)
 
 class SliverForm(forms.ModelForm):
     class Meta:
@@ -1452,7 +1463,7 @@
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
-            kwargs['queryset'] = Site.select_by_user(request.user)
+            kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_users=True)
 
         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
diff --git a/xos/core/models/site.py b/xos/core/models/site.py
index 1e8c7ca..e3c275f 100644
--- a/xos/core/models/site.py
+++ b/xos/core/models/site.py
@@ -99,6 +99,8 @@
     name = StrippedCharField(max_length=200, help_text="Name for this Site")
     site_url = models.URLField(null=True, blank=True, max_length=512, help_text="Site's Home URL Page")
     enabled = models.BooleanField(default=True, help_text="Status for this Site")
+    hosts_nodes = models.BooleanField(default=True, help_text="Indicates whether or not the site host nodes")
+    hosts_users = models.BooleanField(default=True, help_text="Indicates whether or not the site manages user accounts")
     location = GeopositionField()
     longitude = models.FloatField(null=True, blank=True)
     latitude = models.FloatField(null=True, blank=True)
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 795aa92..fc195d2 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -344,8 +344,94 @@
        readable_objects = []
        for model in models:
            readable_objects.extend(model.select_by_user(self))
-       return readable_objects      
+       return readable_objects
 
+    def get_permissions(self, filter=None):
+        """ Return a list of objects for which the user has read or read/write 
+        access. The object will be an instance of a django model object. 
+        Permissions will be either 'r' or 'rw'.
+         
+        e.g.
+        [{'object': django_object_instance, 'permissions': 'rw'}, ...]
+
+        Returns:
+          list of dicts  
+       
+        """
+        from core.models import *
+        READ = 'r'
+        READWRITE = 'rw'
+
+        deployment_priv_objs = [Image, NetworkTemplate, Flavor]
+        site_priv_objs = [Node, Slice, User]
+        slice_priv_objs = [Sliver, Network] 
+        
+        # maps the set of objects a paticular role has write access
+        write_map = {
+            DeploymentPrivilege : {
+                'admin': deployment_priv_objects,
+            },
+            SitePrivilege : {
+                'admin' : site_priv_objs,
+                'pi' : [Slice, User],
+                'tech': [Node],
+            },     
+            SlicePrivilege : {
+                'admin': slice_priv_objs, 
+            }, 
+        }
+            
+        privilege_map = {
+            DeploymentPrivilege : (Deployment, deployment_priv_objs),
+            SitePrivilege : (Site, site_priv_objs),
+            SlicePrivilege : (Slice, slice_priv_objs)
+        }
+        permissions = []
+        permission_dict = lambda x,y: {'object': x, 'permission': y}
+        for privilege_model, (model, affected_models) in privileg_map.items():
+            # get the objects affected by this privilege model   
+            affected_objects = []
+            for affected_model in affected_models:
+                affected_objects.extend(affected_model.select_by_user(self))
+
+            if self.is_admin:
+                # assume admin users have read/write access to all objects
+                for affected_object in affected_objects:
+                    permissions.append(permission_dict(affected_object, READWRITE))
+            else:
+                # create a dict of the user's per object privileges
+                # ex:  {princeton_tmack : ['admin']  
+                privileges = privilege_model.objects.filter(user=self)
+                for privilege in privileges:
+                    object_roles = defaultdict(list)
+                    obj = None
+                    roles = []
+                    for field in dir(privilege):
+                        if field == model.__name__.lower():
+                            obj = getattr(privilege, field)
+                    if obj:
+                        object_roles[obj].append(privilege.role.role)
+                        
+                # loop through all objects the user has access to and determine
+                # if they also have write access
+                for affected_object in affected_objects:
+                    if affected_object not in objects_roles:
+                        permissions.append(permission_dict(affected_object, READ))
+                    else:
+                        has_write_permission = False
+                        for write_role, models in write_dict.items():
+                            if affected_object._meta.model in models and \
+                                write_role in object_roles[affected_object]:
+                                    has_write_permission = True
+                                    break
+                        if has_write_permission:
+                            permissions.append(permission_dict(affected_object, WRITE))
+                        else:
+                            permissions.append(permission_dict(affected_object, READ))
+                                
+        return permissions                          
+                     
+    
     @staticmethod
     def select_by_user(user):
         if user.is_admin: