[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