[CalendarServer-changes] [15498] CalendarServer/trunk/calendarserver

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 1 15:47:08 PDT 2016


Revision: 15498
          http://trac.calendarserver.org//changeset/15498
Author:   sagen at apple.com
Date:     2016-04-01 15:47:08 -0700 (Fri, 01 Apr 2016)
Log Message:
-----------
Port the old webadmin which supports manipulating delegation

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/calendarserver/webadmin/landing.py
    CalendarServer/trunk/calendarserver/webadmin/test/test_eventsource.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/webadmin/delegation.html
    CalendarServer/trunk/calendarserver/webadmin/delegation.py

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2016-04-01 16:43:53 UTC (rev 15497)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2016-04-01 22:47:08 UTC (rev 15498)
@@ -36,7 +36,7 @@
 from calendarserver.push.util import getAPNTopicFromConfig
 from calendarserver.tools import diagnose
 from calendarserver.tools.util import checkDirectory
-from calendarserver.webadmin.landing import WebAdminLandingResource
+from calendarserver.webadmin.delegation import WebAdminResource
 from calendarserver.webcal.resource import WebCalendarResource
 
 from socket import fromfd, AF_UNIX, SOCK_STREAM, socketpair
@@ -420,7 +420,7 @@
     timezoneServiceResourceClass = TimezoneServiceResource
     timezoneStdServiceResourceClass = TimezoneStdServiceResource
     webCalendarResourceClass = WebCalendarResource
-    webAdminResourceClass = WebAdminLandingResource
+    webAdminResourceClass = WebAdminResource
     addressBookResourceClass = DirectoryAddressBookHomeProvisioningResource
     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
     apnSubscriptionResourceClass = APNSubscriptionResource

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2016-04-01 16:43:53 UTC (rev 15497)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2016-04-01 22:47:08 UTC (rev 15498)
@@ -49,7 +49,10 @@
 from txdav.xml import element
 from pycalendar.datetime import DateTime, Timezone
 
+from twext.who.idirectory import RecordType
+from txdav.who.idirectory import RecordType as CalRecordType
 
+
 log = Logger()
 
 
@@ -248,7 +251,18 @@
         if checkOnly:
             returnValue(None)
 
-        recordType, shortName = principalID.split(":", 1)
+        recordTypeDescription, shortName = principalID.split(":", 1)
+        for recordType in (
+            RecordType.user,
+            RecordType.group,
+            CalRecordType.location,
+            CalRecordType.resource,
+            CalRecordType.address,
+        ):
+            if recordType.description == recordTypeDescription:
+                break
+        else:
+            returnValue(None)
 
         returnValue((yield directory.principalCollection.principalForShortName(recordType, shortName)))
 
@@ -366,7 +380,7 @@
 def addProxy(rootResource, directory, store, principal, proxyType, proxyPrincipal):
     proxyURL = proxyPrincipal.url()
 
-    subPrincipal = proxySubprincipal(principal, proxyType)
+    subPrincipal = yield proxySubprincipal(principal, proxyType)
     if subPrincipal is None:
         raise ProxyError(
             "Unable to edit %s proxies for %s\n" % (
@@ -412,7 +426,7 @@
     for proxyType in proxyTypes:
         proxyURL = proxyPrincipal.url()
 
-        subPrincipal = proxySubprincipal(principal, proxyType)
+        subPrincipal = yield proxySubprincipal(principal, proxyType)
         if subPrincipal is None:
             raise ProxyError(
                 "Unable to edit %s proxies for %s\n" % (
@@ -448,7 +462,7 @@
 
 
 def prettyPrincipal(principal):
-    prettyRecord(principal.record)
+    return prettyRecord(principal.record)
 
 
 

Added: CalendarServer/trunk/calendarserver/webadmin/delegation.html
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/delegation.html	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/webadmin/delegation.html	2016-04-01 22:47:08 UTC (rev 15498)
@@ -0,0 +1,208 @@
+<html xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
+      t:render="main">
+  <head>
+    <style>
+      th, .even td, .odd td {
+        padding-right: 0.5em;
+        font-family: monospace;
+      }
+      .even-dir {
+        background-color: #efe0ef;
+      }
+      .even {
+        background-color: #eee;
+      }
+      .odd-dir {
+        background-color: #f0d0ef;
+      }
+      .odd {
+        background-color: #dedede;
+      }
+      .icon {
+        text-align: center;
+      }
+      .listing {
+        margin-left: auto;
+        margin-right: auto;
+        width: 50%;
+        padding: 0.1em;
+      }
+      .content {
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+      body {
+        border: 0;
+        padding: 0;
+        margin: 0;
+        background-color: #efefef;
+      }
+      h1 {
+        padding: 0.1em;
+        padding-left:10px;
+        padding-right:10px;
+        background-color: #777;
+        color: white;
+        border-bottom: thin white dashed;
+      }
+    </style>
+    <title>Calendar Server Web Administration</title>
+  </head>
+  <body>
+    <h1>Calendar Server Web Administration</h1>
+    <div class="content">
+      <h2>Resource Management</h2>
+      <form id="frm_resource" name="resourceForm">
+        Search for resource to manage:
+        <input type="text" id="txt_resourceSearch" name="resourceSearch"
+        size="40"><t:attr name="value"><t:slot name="resourceSearch"
+        /></t:attr></input>
+        <input type="submit" value="Search" />
+      </form>
+      <div t:render="noSearchResults" style="margin-top:4px"
+        >No matches found for resource <b><t:slot name="resourceSearch" /></b>.
+      </div>
+      <table id="tab_searchResults" t:render="hasSearchResults" cellspacing="0"
+             cellpadding="3" border="1" style="margin-top: 2px">
+        <tr class="odd">
+          <th>ID</th>
+          <th>Full Name</th>
+          <th>Type</th>
+          <th>Short Names</th>
+          <th>Email Addresses</th>
+        </tr>
+        <tr t:render="searchResults">
+          <t:attr name="class"><t:slot name="rowClass" /></t:attr>
+          <td><a>select<t:attr name="href" >/admin/?resourceId=<t:slot name="uid" /></t:attr></a></td>
+          <td><t:slot name="name" /></td>
+          <td><t:slot name="typeStr" /></td>
+          <td><t:slot name="shortNames" /></td>
+          <td><t:slot name="emails" /></td>
+        </tr>
+      </table>
+      <div style="margin-top:15px; background-color: #777;
+                  border-bottom:1px #ffffff dotted"></div>
+      <div style="background-color: #777; padding-top:2px;
+                  border-bottom:1px #ffffff dotted"></div>
+
+      <t:transparent t:render="resourceDetails">
+      <h3>Resource Details: <t:slot name="resourceTitle" /></h3>
+
+      <!-- autoScheduleHtml -->
+
+      <t:transparent t:render="autoSchedule">
+      <div style="margin-top:15px; border-bottom:1px #444444 dotted"></div>
+      <form id="frm_autoSchedule" name="autoScheduleForm" action="/admin/"
+        style="margin-top:15px">
+        <input type="hidden" id="hdn_resourceId" name="resourceId"
+        ><t:attr name="value"><t:slot name="resourceId" /></t:attr></input>
+        <div style="margin-top:7px">
+          Auto-Schedule Mode
+          <select id="sel_autoScheduleMode" name="autoScheduleMode">
+            <option t:render="autoScheduleModeNone" value="none">None</option>
+            <option t:render="autoScheduleModeAcceptAlways" value="accept-always">Accept Always</option>
+            <option t:render="autoScheduleModeDeclineAlways" value="decline-always">Decline Always</option>
+            <option t:render="autoScheduleModeAcceptIfFree" value="accept-if-free">Accept If Free</option>
+            <option t:render="autoScheduleModeDeclineIfBusy" value="decline-if-busy">Decline If Busy</option>
+            <option t:render="autoScheduleModeAutomatic" value="automatic">Automatic (Accept and Decline)</option>
+          </select>
+          <br/>
+          <input type="submit" value="Change" />
+        </div>
+      </form>
+      </t:transparent>
+
+      <!-- currentProxiesHtml -->
+      <div style="margin-top:15px; border-bottom:1px #444444 dotted"></div>
+      <form id="frm_proxies" name="proxiesForm" action="/admin/"
+        style="margin-top:15px">
+        <input type="hidden" id="hdn_resourceId" name="resourceId"
+        ><t:attr name="value"><t:slot name="resourceId" /></t:attr></input>
+        <div t:render="noProxies" style="margin-top:15px"
+          >This resource has no proxies.</div>
+        <table cellspacing="0" cellpadding="3" border="1"
+          t:render="hasProxies">
+          <tr class="odd">
+            <th colspan="2">Read-Only Proxies</th>
+            <th colspan="2">Read-Write Proxies</th>
+          </tr>
+          <tr t:render="proxyRows">
+            <t:attr name="class"><t:slot name="rowClass" /> </t:attr>
+            <t:transparent t:render="readOnlyProxies">
+            <td><t:slot name="proxy" /></td>
+            <td>
+              <input type="submit" value="Make Read-Write"><t:attr
+              name="name">mkWriteProxy|<t:slot name="uid"/></t:attr></input>
+              <input type="submit" value="Remove Proxy"><t:attr
+              name="name">rmProxy|<t:slot name="uid"/></t:attr></input>
+            </td>
+            </t:transparent>
+
+            <t:transparent t:render="noReadOnlyProxies">
+            <td colspan="2"></td>
+            </t:transparent>
+            <t:transparent t:render="readWriteProxies">
+            <td><t:slot name="proxy" /></td>
+            <td>
+              <input type="submit" value="Make Read-Only"><t:attr
+              name="name">mkReadProxy|<t:slot name="uid"/></t:attr></input>
+              <input type="submit" value="Remove Proxy"><t:attr
+              name="name">rmProxy|<t:slot name="uid"/></t:attr></input>
+            </td>
+            </t:transparent>
+            <t:transparent t:render="noReadWriteProxies">
+            <td colspan="2"></td>
+            </t:transparent>
+          </tr>
+        </table>
+      </form>
+
+      <!-- proxySearchHtml -->
+
+      <div style="margin-top:15px; border-bottom:1px #444444 dotted"></div>
+      <div t:render="noProxyResults"
+        style="margin-top:4px"
+        >No matches found for proxy resource <b><t:slot
+          name="proxySearch" /></b>.</div>
+      <form id="frm_proxySearch" name="proxySearchForm" action="/admin/"
+        style="margin-top:15px; margin-bottom:0; padding-bottom:0">
+        Search to add proxies:
+        <input type="hidden" id="hdn_resourceId" name="resourceId"
+        ><t:attr name="value"><t:slot name="resourceId" /></t:attr></input>
+        <input type="text" id="txt_proxySearch" name="proxySearch" size="40"
+        ><t:attr name="value"><t:slot name="proxySearch" /></t:attr></input>
+        <input type="submit" value="Search"></input>
+      </form>
+      <form t:render="hasProxyResults"
+        id="frm_proxyAdd" name="proxyAddForm" action="/admin/"
+        style="margin-top:2px; padding-top:0">
+        <input type="hidden" id="hdn_resourceId" name="resourceId"
+        ><t:attr name="value"><t:slot name="resourceId" /></t:attr></input>
+        <table cellspacing="0" cellpadding="3" border="1">
+          <tr class="odd">
+            <th>Full Name</th>
+            <th>Type</th>
+            <th>Short Names</th>
+            <th>Email Addresses</th>
+            <th></th>
+          </tr>
+          <tr t:render="proxySearchRows">
+            <t:attr name="class"><t:slot name="rowClass" /> </t:attr>
+            <td><t:slot name="name" /></td>
+            <td><t:slot name="typeStr" /></td>
+            <td><t:slot name="shortNames" /></td>
+            <td><t:slot name="emails" /></td>
+            <td>
+              <input type="submit" value="Make Read-Only Proxy"><t:attr
+              name="name">mkReadProxy|<t:slot name="uid"/></t:attr></input>
+              <input type="submit" value="Make Read-Write Proxy"><t:attr
+              name="name">mkWriteProxy|<t:slot name="uid"/></t:attr></input>
+            </td>
+          </tr>
+        </table>
+      </form>
+      </t:transparent>
+    </div>
+
+  </body>
+</html>

Added: CalendarServer/trunk/calendarserver/webadmin/delegation.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/delegation.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/webadmin/delegation.py	2016-04-01 22:47:08 UTC (rev 15498)
@@ -0,0 +1,728 @@
+# -*- test-case-name: calendarserver.webadmin.test.test_resource -*-
+##
+# Copyright (c) 2009-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Calendar Server Web Admin UI.
+"""
+
+__all__ = [
+    "WebAdminResource",
+    "WebAdminPage",
+]
+
+import urlparse
+
+from calendarserver.tools.util import (
+    principalForPrincipalID, proxySubprincipal, action_addProxyPrincipal,
+    action_removeProxyPrincipal
+)
+
+from twistedcaldav.config import config
+from twistedcaldav.extensions import DAVFile, ReadOnlyResourceMixIn
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from txweb2.http import Response
+from txweb2.http_headers import MimeType
+from txweb2.stream import MemoryStream
+from twisted.python.modules import getModule
+from zope.interface.declarations import implements
+from txdav.xml import element as davxml
+
+from twisted.web.iweb import ITemplateLoader
+from twisted.web.template import (
+    Element, renderer, XMLFile, flattenString
+)
+from twext.who.idirectory import RecordType
+from txdav.who.idirectory import RecordType as CalRecordType, AutoScheduleMode
+
+
+class WebAdminPage(Element):
+    """
+    Web administration renderer for HTML.
+
+    @ivar resource: a L{WebAdminResource}.
+    """
+
+    loader = XMLFile(
+        getModule(__name__).filePath.sibling("delegation.html")
+    )
+
+    def __init__(self, resource):
+        super(WebAdminPage, self).__init__()
+        self.resource = resource
+
+
+    @renderer
+    def main(self, request, tag):
+        """
+        Main renderer, which fills page-global slots like 'title'.
+        """
+        searchTerm = request.args.get('resourceSearch', [''])[0]
+        return tag.fillSlots(resourceSearch=searchTerm)
+
+
+    @renderer
+    @inlineCallbacks
+    def hasSearchResults(self, request, tag):
+        """
+        Renderer which detects if there are resource search results and
+        continues if so.
+        """
+        if 'resourceSearch' not in request.args:
+            returnValue('')
+        if (yield self.performSearch(request)):
+            returnValue(tag)
+        else:
+            returnValue('')
+
+
+    @renderer
+    @inlineCallbacks
+    def noSearchResults(self, request, tag):
+        """
+        Renderer which detects if there are resource search results and
+        continues if so.
+        """
+        if 'resourceSearch' not in request.args:
+            returnValue('')
+        rows = yield self.performSearch(request)
+        if rows:
+            returnValue("")
+        else:
+            returnValue(tag)
+
+    _searchResults = None
+
+    @inlineCallbacks
+    def performSearch(self, request):
+        """
+        Perform a directory search for users, groups, and resources based on the
+        resourceSearch query parameter.  Cache the results of that search so
+        that it will only be done once per request.
+        """
+        if self._searchResults is not None:
+            returnValue(self._searchResults)
+        searchTerm = request.args.get('resourceSearch', [''])[0]
+        if searchTerm:
+            results = sorted((yield self.resource.search(searchTerm)),
+                             key=lambda record: record.fullNames[0])
+        else:
+            results = []
+        self._searchResults = results
+        returnValue(results)
+
+
+    @renderer
+    def searchResults(self, request, tag):
+        """
+        Renderer which renders resource search results.
+        """
+        d = self.performSearch(request)
+        return d.addCallback(searchToSlots, tag)
+
+
+    @renderer
+    @inlineCallbacks
+    def resourceDetails(self, request, tag):
+        """
+        Renderer which fills slots for details of the resource selected by
+        the resourceId request parameter.
+        """
+        resourceId = request.args.get('resourceId', [''])[0]
+        propertyName = request.args.get('davPropertyName', [''])[0]
+        proxySearch = request.args.get('proxySearch', [''])[0]
+        if resourceId:
+            principalResource = yield self.resource.getResourceById(
+                request, resourceId)
+            returnValue(
+                DetailsElement(
+                    resourceId, principalResource, propertyName, proxySearch,
+                    tag, self.resource
+                )
+            )
+        else:
+            returnValue("")
+
+
+
+def searchToSlots(results, tag):
+    """
+    Convert the result of doing a search to an iterable of tags.
+    """
+    for idx, record in enumerate(results):
+        if hasattr(record, "shortNames"):
+            shortName = record.shortNames[0]
+            shortNames = record.shortNames
+        else:
+            shortName = "(none)"
+            shortNames = [shortName]
+        if hasattr(record, "emailAddresses"):
+            emailAddresses = record.emailAddresses
+        else:
+            emailAddresses = ["(none)"]
+        yield tag.clone().fillSlots(
+            rowClass="even" if (idx % 2 == 0) else "odd",
+            type=record.recordType.description,
+            shortName=shortName,
+            name=record.fullNames[0],
+            typeStr={
+                RecordType.user        : "User",
+                RecordType.group       : "Group",
+                CalRecordType.location : "Location",
+                CalRecordType.resource : "Resource",
+                CalRecordType.address  : "Address",
+            }.get(record.recordType),
+            shortNames=str(", ".join(shortNames)),
+            emails=str(", ".join(emailAddresses)),
+            uid=str(record.uid),
+        )
+
+
+
+class stan(object):
+    """
+    L{ITemplateLoader} wrapper for an existing tag, in the style of Nevow's
+    'stan' loader.
+    """
+    implements(ITemplateLoader)
+
+    def __init__(self, tag):
+        self.tag = tag
+
+
+    def load(self):
+        return self.tag
+
+
+def recordTitle(record):
+    return u"{} ({} {})".format(record.fullNames[0], record.recordType.description, record.uid)
+
+
+class DetailsElement(Element):
+
+    def __init__(self, resourceId, principalResource, davPropertyName,
+                 proxySearch, tag, adminResource):
+        self.principalResource = principalResource
+        self.adminResource = adminResource
+        self.proxySearch = proxySearch
+        record = principalResource.record
+        tag.fillSlots(resourceTitle=recordTitle(record),
+                      resourceId=resourceId,
+                      davPropertyName=davPropertyName,
+                      proxySearch=proxySearch)
+        try:
+            namespace, name = davPropertyName.split("#")
+        except Exception:
+            self.namespace = None
+            self.name = None
+            if davPropertyName:
+                self.error = davPropertyName
+            else:
+                self.error = None
+        else:
+            self.namespace = namespace
+            self.name = name
+            self.error = None
+
+        super(DetailsElement, self).__init__(loader=stan(tag))
+
+
+    @renderer
+    def propertyParseError(self, request, tag):
+        """
+        Renderer to display an error when the user specifies an invalid property
+        name.
+        """
+        if self.error is None:
+            return ""
+        else:
+            return tag.fillSlots(davPropertyName=self.error)
+
+
+    @renderer
+    @inlineCallbacks
+    def davProperty(self, request, tag):
+        """
+        Renderer to display an error when the user specifies an invalid property
+        name.
+        """
+        if self.name is not None:
+            try:
+                propval = yield self.principalResource.readProperty(
+                    (self.namespace, self.name), request
+                )
+            except:
+                propval = "No such property: " + "#".join([self.namespace,
+                                                           self.name])
+            else:
+                propval = propval.toxml()
+            returnValue(tag.fillSlots(value=propval))
+        else:
+            returnValue("")
+
+
+    @renderer
+    def autoSchedule(self, request, tag):
+        """
+        Renderer which elides its tag for non-resource-type principals.
+        """
+        if (
+            self.principalResource.record.recordType.description != "user" and
+            self.principalResource.record.recordType.description != "group" or
+            self.principalResource.record.recordType.description == "user" and
+            config.Scheduling.Options.AutoSchedule.AllowUsers
+        ):
+            return tag
+        return ""
+
+
+    @renderer
+    def isAutoSchedule(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag if the resource
+        is auto-schedule.
+        """
+        if self.principalResource.getAutoScheduleMode() is not AutoScheduleMode.none:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def isntAutoSchedule(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag if the resource
+        is not auto-schedule.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.none:
+            tag(selected='selected')
+        return tag
+
+
+    # @renderer
+    # def autoScheduleModeDefault(self, request, tag):
+    #     """
+    #     Renderer which sets the 'selected' attribute on its tag based on the resource
+    #     auto-schedule-mode.
+    #     """
+    #     if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.default:
+    #         tag(selected='selected')
+    #     return tag
+
+
+    @renderer
+    def autoScheduleModeNone(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.none:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def autoScheduleModeAcceptAlways(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.accept:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def autoScheduleModeDeclineAlways(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.decline:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def autoScheduleModeAcceptIfFree(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.acceptIfFree:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def autoScheduleModeDeclineIfBusy(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.declineIfBusy:
+            tag(selected='selected')
+        return tag
+
+
+    @renderer
+    def autoScheduleModeAutomatic(self, request, tag):
+        """
+        Renderer which sets the 'selected' attribute on its tag based on the resource
+        auto-schedule-mode.
+        """
+        if self.principalResource.getAutoScheduleMode() is AutoScheduleMode.acceptIfFreeDeclineIfBusy:
+            tag(selected='selected')
+        return tag
+
+    _matrix = None
+
+    @inlineCallbacks
+    def proxyMatrix(self, request):
+        """
+        Compute a matrix of proxies to display in a 2-column table.
+
+        This value is cached so that multiple renderers may refer to it without
+        causing additional back-end queries.
+
+        @return: a L{Deferred} which fires with a list of 2-tuples of
+            (readProxy, writeProxy).  If there is an unequal number of read and
+            write proxies, the tables will be padded out with C{None}s so that
+            some readProxy or writeProxy values will be C{None} at the end of
+            the table.
+        """
+        if self._matrix is not None:
+            returnValue(self._matrix)
+        (readSubPrincipal, writeSubPrincipal) = (
+            (yield proxySubprincipal(self.principalResource, "read")),
+            (yield proxySubprincipal(self.principalResource, "write"))
+        )
+        if readSubPrincipal or writeSubPrincipal:
+            (readMembers, writeMembers) = (
+                (yield readSubPrincipal.readProperty(davxml.GroupMemberSet,
+                                                     None)),
+                (yield writeSubPrincipal.readProperty(davxml.GroupMemberSet,
+                                                      None))
+            )
+            if readMembers.children or writeMembers.children:
+                # FIXME: 'else' case needs to be handled by separate renderer
+                readProxies = []
+                writeProxies = []
+                def getres(ref):
+                    return self.adminResource.getResourceById(request,
+                                                              str(proxyHRef))
+                for proxyHRef in sorted(readMembers.children, key=str):
+                    readProxies.append((yield getres(proxyHRef)))
+                for proxyHRef in sorted(writeMembers.children, key=str):
+                    writeProxies.append((yield getres(proxyHRef)))
+                lendiff = len(readProxies) - len(writeProxies)
+                if lendiff > 0:
+                    writeProxies += [None] * lendiff
+                elif lendiff < 0:
+                    readProxies += [None] * -lendiff
+                self._matrix = zip(readProxies, writeProxies)
+            else:
+                self._matrix = []
+        else:
+            self._matrix = []
+        returnValue(self._matrix)
+
+
+    @renderer
+    @inlineCallbacks
+    def noProxies(self, request, tag):
+        """
+        Renderer which shows its tag if there are no proxies for this resource.
+        """
+        mtx = yield self.proxyMatrix(request)
+        if mtx:
+            returnValue("")
+        returnValue(tag)
+
+
+    @renderer
+    @inlineCallbacks
+    def hasProxies(self, request, tag):
+        """
+        Renderer which shows its tag if there are any proxies for this resource.
+        """
+        mtx = yield self.proxyMatrix(request)
+        if mtx:
+            returnValue(tag)
+        returnValue("")
+
+
+    @renderer
+    @inlineCallbacks
+    def noProxyResults(self, request, tag):
+        """
+        Renderer which shows its tag if there are no proxy search results for
+        this request.
+        """
+        if not self.proxySearch:
+            returnValue("")
+        results = yield self.performProxySearch()
+        if results:
+            returnValue("")
+        else:
+            returnValue(tag)
+
+
+    @renderer
+    @inlineCallbacks
+    def hasProxyResults(self, request, tag):
+        """
+        Renderer which shows its tag if there are any proxy search results for
+        this request.
+        """
+        results = yield self.performProxySearch()
+        if results:
+            returnValue(tag)
+        else:
+            returnValue("")
+
+
+    @renderer
+    @inlineCallbacks
+    def proxyRows(self, request, tag):
+        """
+        Renderer which does zipping logic to render read-only and read-write
+        rows of existing proxies for the currently-viewed resource.
+        """
+        result = []
+        mtx = yield self.proxyMatrix(request)
+        for idx, (readProxy, writeProxy) in enumerate(mtx):
+            result.append(ProxyRow(tag.clone(), idx, readProxy, writeProxy))
+        returnValue(result)
+
+    _proxySearchResults = None
+
+    def performProxySearch(self):
+        if self._proxySearchResults is not None:
+            return succeed(self._proxySearchResults)
+
+        if self.proxySearch:
+            def nameSorted(records):
+                self._proxySearchResults = sorted(records, key=lambda rec: rec.fullNames[0])
+                return records
+            return self.adminResource.search(
+                self.proxySearch).addCallback(nameSorted)
+        else:
+            return succeed([])
+
+
+    @renderer
+    def proxySearchRows(self, request, tag):
+        """
+        Renderer which renders search results for the proxy form.
+        """
+        d = self.performProxySearch()
+        return d.addCallback(searchToSlots, tag)
+
+
+
+class ProxyRow(Element):
+
+    def __init__(self, tag, index, readProxy, writeProxy):
+        tag.fillSlots(rowClass="even" if (index % 2 == 0) else "odd")
+        super(ProxyRow, self).__init__(loader=stan(tag))
+        self.readProxy = readProxy
+        self.writeProxy = writeProxy
+
+
+    def proxies(self, proxyResource, tag):
+        if proxyResource is None:
+            return ''
+        return tag.fillSlots(proxy=recordTitle(proxyResource.record),
+                             type=proxyResource.record.recordType.description,
+                             fullName=proxyResource.record.fullNames[0],
+                             uid=proxyResource.record.uid)
+
+
+    def noProxies(self, proxyResource, tag):
+        if proxyResource is None:
+            return tag
+        else:
+            return ""
+
+
+    @renderer
+    def readOnlyProxies(self, request, tag):
+        return self.proxies(self.readProxy, tag)
+
+
+    @renderer
+    def noReadOnlyProxies(self, request, tag):
+        return self.noProxies(self.readProxy, tag)
+
+
+    @renderer
+    def readWriteProxies(self, request, tag):
+        return self.proxies(self.writeProxy, tag)
+
+
+    @renderer
+    def noReadWriteProxies(self, request, tag):
+        return self.noProxies(self.writeProxy, tag)
+
+
+
+class WebAdminResource (ReadOnlyResourceMixIn, DAVFile):
+    """
+    Web administration HTTP resource.
+    """
+
+    def __init__(self, path, root, directory, store, principalCollections=()):
+        self.root = root
+        self.directory = directory
+        self.store = store
+        super(WebAdminResource, self).__init__(
+            path,
+            principalCollections=principalCollections
+        )
+
+
+    # Only allow administrators to access
+    def defaultAccessControlList(self):
+        return davxml.ACL(*config.AdminACEs)
+
+
+    def etag(self):
+        # Can't be calculated here
+        return succeed(None)
+
+
+    def contentLength(self):
+        # Can't be calculated here
+        return None
+
+
+    def lastModified(self):
+        return None
+
+
+    def exists(self):
+        return True
+
+
+    def displayName(self):
+        return "Web Admin"
+
+
+    def contentType(self):
+        return MimeType.fromString("text/html; charset=utf-8")
+
+
+    def contentEncoding(self):
+        return None
+
+
+    def createSimilarFile(self, path):
+        return DAVFile(path, principalCollections=self.principalCollections())
+
+
+    @inlineCallbacks
+    def resourceActions(self, request, principal):
+        """
+        Take all actions on the given principal based on the given request.
+        """
+
+        def queryValue(arg):
+            return request.args.get(arg, [""])[0]
+
+        def queryValues(arg):
+            query = urlparse.parse_qs(urlparse.urlparse(request.uri).query,
+                                      True)
+            matches = []
+            for key in query.keys():
+                if key.startswith(arg):
+                    matches.append(key[len(arg):])
+            return matches
+
+        autoSchedule = queryValue("autoSchedule")
+        autoScheduleMode = queryValue("autoScheduleMode")
+        makeReadProxies = queryValues("mkReadProxy|")
+        makeWriteProxies = queryValues("mkWriteProxy|")
+        removeProxies = queryValues("rmProxy|")
+
+        # Update the auto-schedule value if specified.
+        if autoSchedule is not None and (autoSchedule == "true" or
+                                         autoSchedule == "false"):
+            if (
+                principal.record.recordType != RecordType.user and
+                principal.record.recordType != RecordType.group or
+                principal.record.recordType == RecordType.user and
+                config.Scheduling.Options.AutoSchedule.AllowUsers
+            ):
+                (yield principal.setAutoSchedule(autoSchedule == "true"))
+                (yield principal.setAutoScheduleMode(autoScheduleMode))
+
+        # Update the proxies if specified.
+        for proxyId in removeProxies:
+            proxy = yield self.getResourceById(request, proxyId)
+            yield action_removeProxyPrincipal(
+                self.root, self.directory, self.store,
+                principal, proxy, proxyTypes=["read", "write"]
+            )
+
+        for proxyId in makeReadProxies:
+            proxy = yield self.getResourceById(request, proxyId)
+            yield action_addProxyPrincipal(
+                self.root, self.directory, self.store,
+                principal, "read", proxy
+            )
+
+        for proxyId in makeWriteProxies:
+            proxy = yield self.getResourceById(request, proxyId)
+            yield action_addProxyPrincipal(
+                self.root, self.directory, self.store,
+                principal, "write", proxy
+            )
+
+
+    @inlineCallbacks
+    def render(self, request):
+        """
+        Create a L{WebAdminPage} to render HTML content for this request, and
+        return a response.
+        """
+        resourceId = request.args.get('resourceId', [''])[0]
+        if resourceId:
+            principal = yield self.getResourceById(request, resourceId)
+            yield self.resourceActions(request, principal)
+        htmlContent = yield flattenString(request, WebAdminPage(self))
+        response = Response()
+        response.stream = MemoryStream(htmlContent)
+        for (header, value) in (
+                ("content-type", self.contentType()),
+                ("content-encoding", self.contentEncoding()),
+        ):
+            if value is not None:
+                response.headers.setHeader(header, value)
+        returnValue(response)
+
+
+    def getResourceById(self, request, resourceId):
+        if resourceId.startswith("/"):
+            return request.locateResource(resourceId)
+        else:
+            return principalForPrincipalID(resourceId, directory=self.directory)
+
+
+    @inlineCallbacks
+    def search(self, searchStr):
+        records = list((yield self.directory.recordsMatchingTokens(searchStr.strip().split())))
+        returnValue(records)

Modified: CalendarServer/trunk/calendarserver/webadmin/landing.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/landing.py	2016-04-01 16:43:53 UTC (rev 15497)
+++ CalendarServer/trunk/calendarserver/webadmin/landing.py	2016-04-01 22:47:08 UTC (rev 15498)
@@ -71,8 +71,8 @@
         from .principals import PrincipalsResource
         self.putChild(u"principals", PrincipalsResource(directory, store, principalCollections))
 
-        from .logs import LogsResource
-        self.putChild(u"logs", LogsResource(principalCollections))
+        # from .logs import LogsResource
+        # self.putChild(u"logs", LogsResource(principalCollections))
 
         # from .work import WorkMonitorResource
         # self.putChild(u"work", WorkMonitorResource(store))

Modified: CalendarServer/trunk/calendarserver/webadmin/test/test_eventsource.py
===================================================================
--- CalendarServer/trunk/calendarserver/webadmin/test/test_eventsource.py	2016-04-01 16:43:53 UTC (rev 15497)
+++ CalendarServer/trunk/calendarserver/webadmin/test/test_eventsource.py	2016-04-01 22:47:08 UTC (rev 15498)
@@ -37,6 +37,8 @@
     Tests for emitting HTML5 EventSource events.
     """
 
+    todo = "Disabling new webadmin"
+
     def test_textAsEvent(self):
         """
         Generate an event from some text.
@@ -120,6 +122,8 @@
     Tests for L{EventSourceResource}.
     """
 
+    todo = "Disabling new webadmin"
+
     def eventSourceResource(self):
         return EventSourceResource(DictionaryEventDecoder, None)
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20160401/a9037a26/attachment-0001.html>


More information about the calendarserver-changes mailing list