├── .gitignore
├── .project
├── .pydevproject
├── .twext.sublime-project
├── CONTRIBUTING.md
├── LICENSE.txt
├── PULL_REQUEST_TEMPLATE.md
├── README.rst
├── bin
├── _build.sh
├── _py.sh
├── develop
├── environment
├── gendocs
├── pyflakes
├── python
├── test
├── test_opendirectory_auth
├── test_opendirectory_lookup
├── trial
├── twistd
└── update_copyrights
├── requirements-dev.txt
├── setup.py
├── twext
├── __init__.py
├── application
│ ├── __init__.py
│ ├── masterchild.py
│ └── service.py
├── enterprise
│ ├── __init__.py
│ ├── adbapi2.py
│ ├── dal
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── parseschema.py
│ │ ├── record.py
│ │ ├── syntax.py
│ │ └── test
│ │ │ ├── __init__.py
│ │ │ ├── test_parseschema.py
│ │ │ ├── test_record.py
│ │ │ └── test_sqlsyntax.py
│ ├── fixtures.py
│ ├── ienterprise.py
│ ├── jobs
│ │ ├── __init__.py
│ │ ├── jobitem.py
│ │ ├── queue.py
│ │ ├── test
│ │ │ ├── __init__.py
│ │ │ └── test_jobs.py
│ │ ├── utils.py
│ │ └── workitem.py
│ ├── locking.py
│ ├── queue.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── test_adbapi2.py
│ │ ├── test_fixtures.py
│ │ ├── test_locking.py
│ │ ├── test_queue.py
│ │ └── test_util.py
│ └── util.py
├── internet
│ ├── __init__.py
│ ├── adaptendpoint.py
│ ├── decorate.py
│ ├── fswatch.py
│ ├── gaiendpoint.py
│ ├── sendfdport.py
│ ├── socketfile.py
│ ├── spawnsvc.py
│ ├── ssl.py
│ ├── tcp.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── test_adaptendpoint.py
│ │ ├── test_fswatch.py
│ │ ├── test_gaiendpoint.py
│ │ └── test_sendfdport.py
│ └── threadutils.py
├── protocols
│ ├── __init__.py
│ ├── echo.py
│ └── test
│ │ └── __init__.py
├── python
│ ├── __init__.py
│ ├── clsprop.py
│ ├── filepath.py
│ ├── launchd.py
│ ├── log.py
│ ├── parallel.py
│ ├── sacl.py
│ ├── sendfd.py
│ ├── test
│ │ ├── __init__.py
│ │ ├── pullpipe.py
│ │ ├── test_filepath.py
│ │ ├── test_launchd.py
│ │ ├── test_log.py
│ │ └── test_parallel.py
│ ├── types.py
│ └── usage.py
└── who
│ ├── __init__.py
│ ├── aggregate.py
│ ├── checker.py
│ ├── directory.py
│ ├── expression.py
│ ├── idirectory.py
│ ├── index.py
│ ├── ldap
│ ├── __init__.py
│ ├── _constants.py
│ ├── _service.py
│ ├── _util.py
│ └── test
│ │ ├── __init__.py
│ │ ├── test_service.py
│ │ └── test_util.py
│ ├── opendirectory
│ ├── __init__.py
│ ├── _constants.py
│ ├── _odframework.py
│ ├── _scripts.py
│ ├── _service.py
│ └── test
│ │ ├── __init__.py
│ │ └── test_service.py
│ ├── test
│ ├── __init__.py
│ ├── auth_resource.rpy
│ ├── test_aggregate.py
│ ├── test_concurrency.py
│ ├── test_directory.py
│ ├── test_expression.py
│ ├── test_index.py
│ ├── test_util.py
│ └── test_xml.py
│ ├── util.py
│ └── xml.py
└── twisted
└── plugins
└── masterchild.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info/
2 | *.o
3 | *.py[co]
4 | *.so
5 | _trial_temp*/
6 | build/
7 | dropin.cache
8 | *~
9 | *.lock
10 | .develop/
11 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | twextpy
4 |
5 |
6 | Twisted
7 |
8 |
9 |
10 | org.python.pydev.PyDevBuilder
11 |
12 |
13 |
14 |
15 |
16 | org.python.pydev.pythonNature
17 |
18 |
19 |
20 | 0
21 |
22 | 10
23 |
24 | org.eclipse.ui.ide.multiFilter
25 | 1.0-projectRelativePath-matches-true-false-.develop
26 |
27 |
28 |
29 | 0
30 |
31 | 10
32 |
33 | org.eclipse.ui.ide.multiFilter
34 | 1.0-projectRelativePath-matches-false-false-build
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 | python 2.7
4 | Default
5 |
6 | /${PROJECT_DIR_NAME}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.twext.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "build_systems":
3 | [
4 | {
5 | "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
6 | "name": "Anaconda Python Builder",
7 | "selector": "source.python",
8 | "shell_cmd": "\"./bin/python\" -u \"$file\""
9 | },
10 | {
11 | "cmd":
12 | [
13 | "${project_path}/bin/trial",
14 | "--temp-directory=${project_path}/.develop/trial",
15 | "--random=0",
16 | "--testmodule=${file}"
17 | ],
18 | "name": "Trial",
19 | "selector": "source.python"
20 | }
21 | ],
22 | "folders":
23 | [
24 | {
25 | "file_exclude_patterns":
26 | [
27 | ".project",
28 | ".pydevproject",
29 | "*.pyc",
30 | "*.pyo"
31 | ],
32 | "folder_exclude_patterns":
33 | [
34 | ".git",
35 | ".develop",
36 | "*.egg-info"
37 | ],
38 | "name": "twext",
39 | "path": "."
40 | }
41 | ],
42 | "settings":
43 | {
44 | "pep257": true,
45 | "pep257_ignore":
46 | [
47 | "D102",
48 | "D105",
49 | "D200",
50 | "D203",
51 | "D205",
52 | "D400"
53 | ],
54 | "pep8_ignore":
55 | [
56 | "E202",
57 | "E203",
58 | "E221",
59 | "E302",
60 | "E303",
61 | "E402"
62 | ],
63 | "pep8_max_line_length": 80,
64 | "pyflakes_ignore_import_*": false,
65 | "python_interpreter": "./bin/python",
66 | "tab_size": 4,
67 | "test_command": "./bin/trial",
68 | "test_delimeter": ".",
69 | "test_project_path": "twext"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | By submitting a request, you represent that you have the right to license
2 | your contribution to Apple and the community, and agree that your
3 | contributions are licensed under the [Apache License Version 2.0](LICENSE.txt).
4 |
5 | For existing files modified by your request, you represent that you have
6 | retained any existing copyright notices and licensing terms. For each new
7 | file in your request, you represent that you have added to the file a
8 | copyright notice (including the year and the copyright owner's name) and the
9 | Calendar and Contacts Server's licensing terms.
10 |
11 | Before submitting the request, please make sure that your request follows
12 | the [Calendar and Contacts Server's guidelines for contributing
13 | code](../../../ccs-calendarserver/blob/master/HACKING.rst).
14 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | By submitting a request, you represent that you have the right to license
2 | your contribution to Apple and the community, and agree that your
3 | contributions are licensed under the [Apache License Version 2.0](LICENSE.txt).
4 |
5 | For existing files modified by your request, you represent that you have
6 | retained any existing copyright notices and licensing terms. For each new
7 | file in your request, you represent that you have added to the file a
8 | copyright notice (including the year and the copyright owner's name) and the
9 | Calendar and Contacts Server's licensing terms.
10 |
11 | Before submitting the request, please make sure that your request follows
12 | the [Calendar and Contacts Server's guidelines for contributing
13 | code](../../../ccs-calendarserver/blob/master/HACKING.rst).
14 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 |
4 | This is a python module consisting of extensions to the Twisted Framework
5 | (http://twistedmatrix.com/).
6 |
7 |
8 | Copyright and License
9 | =====================
10 |
11 | Copyright (c) 2005-2017 Apple Inc. All rights reserved.
12 |
13 | This software is licensed under the Apache License, Version 2.0.
14 |
15 | See the file LICENSE_ for the full text of the license terms.
16 |
17 | .. _LICENSE: LICENSE.txt
18 |
19 |
20 | Installation
21 | ============
22 |
23 | Python version 2.7 is supported.
24 |
25 | This library is can be built using the standard distutils mechanisms.
26 |
27 | It is also registered with the Python Package Index (PyPI) as ``twextpy``
28 | (the name ``twext`` is used by another module in PyPI) for use with ``pip`` and
29 | ``easy_install``::
30 |
31 | pip install twextpy
32 |
33 | This will build and install the ``twext`` module along with its base
34 | dependencies. This library has a number of optional features which must be
35 | specified in order to download build and install their dependencies, for
36 | example::
37 |
38 | pip install twextpy[DAL,Postgres]
39 |
40 | These features are:
41 |
42 | DAL
43 | Enables use of the Database Abstraction Layer implemented in
44 | ``twext.enterprise.dal``.
45 |
46 | LDAP
47 | Enables support for the Lightweight Directory Access Protocol in
48 | ``twext.who.ldap``.
49 |
50 | OpenDirectory
51 | Enables support for the (Mac OS) OpenDirectory framework in
52 | ``twext.who.opendirectory``.
53 |
54 | Oracle
55 | Enables support for Oracle database connectivity in ``twext.enterprise`` and
56 | Oracle syntax in ``twext.enterprise.dal``.
57 |
58 | Postgres
59 | Enables support for Postgres database connectivity in ``twext.enterprise``.
60 |
61 |
62 | Development
63 | ===========
64 |
65 | If you are planning to work on this library, you can manage this with standard
66 | distutils mechanisms. There are, however, some tools in the ``bin`` directory
67 | which automate this management for you.
68 |
69 | To use these tools, you must have ``pip`` on your system.
70 | If you do not have ``pip``, instructions for installing it are at
71 | http://www.pip-installer.org/en/latest/installing.html.
72 | The script ``install_pip`` (requires ``sudo`` access) automates this for you.
73 |
74 | The shell script ``develop`` downloads and builds any dependancies and sets up a
75 | development environment. ``develop`` handles non-Python as well as Python
76 | dependancies.
77 |
78 | The tools ``python``, ``pyflakes``, ``trial``, and ``twistd`` are wrappers
79 | around the cooresponding commands that use ``develop`` to ensure that
80 | dependancies are available, ``PYTHONPATH`` is set up correctly, etc.
81 |
82 | ``test`` runs all of the unit tests and does linting. It should be run before
83 | checking in any code.
84 |
--------------------------------------------------------------------------------
/bin/_py.sh:
--------------------------------------------------------------------------------
1 | # -*- sh-basic-offset: 2 -*-
2 | ##
3 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | find_cmd () {
19 | local cmd="$1"; shift;
20 |
21 | local path="$(type "${cmd}" 2>/dev/null | sed "s|^${cmd} is \(a tracked alias for \)\{0,1\}||")";
22 |
23 | if [ -z "${cmd}" ]; then
24 | return 1;
25 | fi;
26 |
27 | echo "${path}";
28 | }
29 |
30 | # Echo the major.minor version of the given Python interpreter.
31 |
32 | py_version () {
33 | local python="$1"; shift;
34 | echo "$("${python}" -c "from distutils.sysconfig import get_python_version; print get_python_version()")";
35 | }
36 |
37 | #
38 | # Test if a particular python interpreter is available, given the full path to
39 | # that interpreter.
40 | #
41 | try_python () {
42 | local python="$1"; shift;
43 |
44 | if [ -z "${python}" ]; then
45 | return 1;
46 | fi;
47 |
48 | if ! type "${python}" > /dev/null 2>&1; then
49 | return 1;
50 | fi;
51 |
52 | local py_version="$(py_version "${python}")";
53 | if [ "$(echo "${py_version}" | sed 's|\.||')" -lt "25" ]; then
54 | return 1;
55 | fi;
56 | return 0;
57 | }
58 |
59 |
60 | #
61 | # Detect which version of Python to use, then print out which one was detected.
62 | #
63 | # This will prefer the python interpreter in the PYTHON environment variable.
64 | # If that's not found, it will check for "python2.7" and "python", looking for
65 | # each in your PATH.
66 | #
67 | detect_python_version () {
68 | local v;
69 | local p;
70 | for v in "2.7" ""
71 | do
72 | for p in \
73 | "${PYTHON:=}" \
74 | "python${v}" \
75 | ;
76 | do
77 | if p="$(find_cmd "${p}")"; then
78 | if try_python "${p}"; then
79 | echo "${p}";
80 | return 0;
81 | fi;
82 | fi;
83 | done;
84 | done;
85 | return 1;
86 | }
87 |
88 |
89 | #
90 | # Compare version numbers
91 | #
92 | cmp_version () {
93 | local v="$1"; shift;
94 | local mv="$1"; shift;
95 |
96 | local vh;
97 | local mvh;
98 | local result;
99 |
100 | while true; do
101 | vh="${v%%.*}"; # Get highest-order segment
102 | mvh="${mv%%.*}";
103 |
104 | if [ "${vh}" -gt "${mvh}" ]; then
105 | result=1;
106 | break;
107 | fi;
108 |
109 | if [ "${vh}" -lt "${mvh}" ]; then
110 | result=0;
111 | break;
112 | fi;
113 |
114 | if [ "${v}" = "${v#*.}" ]; then
115 | # No dots left, so we're ok
116 | result=0;
117 | break;
118 | fi;
119 |
120 | if [ "${mv}" = "${mv#*.}" ]; then
121 | # No dots left, so we're not gonna match
122 | result=1;
123 | break;
124 | fi;
125 |
126 | v="${v#*.}";
127 | mv="${mv#*.}";
128 | done;
129 |
130 | return ${result};
131 | }
132 |
133 |
134 | #
135 | # Detect which python to use, and store it in the 'python' variable, as well as
136 | # setting up variables related to version and build configuration.
137 | #
138 | init_py () {
139 | # First, detect the appropriate version of Python to use, based on our version
140 | # requirements and the environment. Note that all invocations of python in
141 | # our build scripts should therefore be '"${python}"', not 'python'; this is
142 | # important on systems with older system pythons (2.4 or earlier) with an
143 | # alternate install of Python, or alternate python installation mechanisms
144 | # like virtualenv.
145 | bootstrap_python="$(detect_python_version)";
146 |
147 | # Set the $PYTHON environment variable to an absolute path pointing at the
148 | # appropriate python executable, a standard-ish mechanism used by certain
149 | # non-distutils things that need to find the "right" python. For instance,
150 | # the part of the PostgreSQL build process which builds pl_python. Note that
151 | # detect_python_version, above, already honors $PYTHON, so if this is already
152 | # set it won't be stomped on, it will just be re-set to the same value.
153 | export PYTHON="$(find_cmd ${bootstrap_python})";
154 |
155 | if [ -z "${bootstrap_python:-}" ]; then
156 | echo "No suitable python found. Python 2.6 or 2.7 is required.";
157 | exit 1;
158 | fi;
159 | }
160 |
--------------------------------------------------------------------------------
/bin/develop:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e;
21 | set -u;
22 |
23 | if [ -z "${wd:-}" ]; then
24 | wd="$(cd "$(dirname "$0")/.." && pwd)";
25 | fi;
26 |
27 | . "${wd}/bin/_build.sh";
28 |
29 | develop;
30 |
--------------------------------------------------------------------------------
/bin/environment:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e
21 | set -u
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd)";
24 |
25 | . "${wd}/bin/_build.sh";
26 |
27 | do_setup="false";
28 |
29 | develop > /dev/null;
30 |
31 | if [ $# -gt 0 ]; then
32 | for name in "$@"; do
33 | # Use python to avoid using eval.
34 | "${python}" -c 'import os; print os.environ.get("'${name}'", "")';
35 | done;
36 | else
37 | env;
38 | fi;
39 |
--------------------------------------------------------------------------------
/bin/gendocs:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2014-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e;
21 | set -u;
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd -L)";
24 |
25 | export TWEXT_DEVELOP="true";
26 |
27 | . "${wd}/bin/_build.sh";
28 |
29 | init_build > /dev/null;
30 | py_dependencies || true;
31 |
32 | echo "Installing/updating pydoctor...";
33 | "${python}" -m pip install twisted nevow pydoctor --upgrade;
34 |
35 | pydoctor="${py_bindir}/pydoctor";
36 |
37 | "${pydoctor}" \
38 | --project-name twext \
39 | --project-url "$(setup_print url)" \
40 | --system-class pydoctor.twistedmodel.TwistedSystem \
41 | --project-base-dir "${wd}" \
42 | --add-package "${wd}/twext" \
43 | --html-output "${wd}/docs/api" \
44 | --html-write-function-pages \
45 | --make-html \
46 | ;
47 |
48 | # --quiet
49 |
--------------------------------------------------------------------------------
/bin/pyflakes:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e;
21 | set -u;
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd -L)";
24 |
25 | . "${wd}/bin/_build.sh";
26 |
27 | init_build > /dev/null;
28 |
29 | if [ $# -eq 0 ]; then
30 | set - calendarserver contrib twisted twistedcaldav txdav txweb2;
31 | fi;
32 |
33 | echo "Checking modules:" "$@";
34 | exec "${python}" -m pyflakes "$@";
35 |
--------------------------------------------------------------------------------
/bin/python:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e
21 | set -u
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd)";
24 |
25 | . "${wd}/bin/_build.sh";
26 |
27 | do_setup="false";
28 |
29 | develop > /dev/null;
30 |
31 | exec "${python}" "$@";
32 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e;
21 | set -u;
22 |
23 | #
24 | # Initialize build support
25 | #
26 |
27 | wd="$(cd "$(dirname "$0")/.." && pwd -L)";
28 |
29 | . "${wd}/bin/_build.sh";
30 |
31 | init_build > /dev/null;
32 |
33 |
34 |
35 | #
36 | # Options
37 | #
38 |
39 | do_setup="false";
40 | do_get="false";
41 |
42 | random="--random=$(date "+%s")";
43 | no_color="";
44 | until_fail="";
45 | coverage="";
46 | numjobs="";
47 | reactor="";
48 |
49 | if [ "$(uname -s)" == "Darwin" ]; then
50 | reactor="--reactor=kqueue";
51 | fi;
52 |
53 | usage ()
54 | {
55 | program="$(basename "$0")";
56 |
57 | if [ "${1--}" != "-" ]; then echo "$@"; echo; fi;
58 |
59 | echo "Usage: ${program} [options]";
60 | echo "Options:";
61 | echo " -h Print this help and exit";
62 | echo " -n Do not use color";
63 | echo " -o Do not run tests in random order.";
64 | echo " -r Use specified seed to determine order.";
65 | echo " -u Run until the tests fail.";
66 | echo " -c Generate coverage reports.";
67 |
68 | if [ "${1-}" == "-" ]; then return 0; fi;
69 | exit 64;
70 | }
71 |
72 | while getopts "nhoucr:j:" option; do
73 | case "${option}" in
74 | '?') usage; ;;
75 | 'h') usage -; exit 0; ;;
76 | 'o') random=""; ;;
77 | 'r') random="--random=$OPTARG"; ;;
78 | 'n') no_color="--reporter=bwverbose"; ;;
79 | 'u') until_fail="--until-failure"; ;;
80 | 'c') coverage="--coverage"; ;;
81 | 'j') numjobs="-j $OPTARG"; ;;
82 | esac;
83 | done;
84 | shift $((${OPTIND} - 1));
85 |
86 | if [ $# -eq 0 ]; then
87 | lint="true";
88 | set - twext;
89 | else
90 | lint="false";
91 | fi;
92 |
93 |
94 |
95 | #
96 | # Dependencies
97 | #
98 |
99 | c_dependencies >> "${dev_home}/setup.log";
100 | py_dependencies >> "${dev_home}/setup.log";
101 |
102 |
103 |
104 | #
105 | # Clean up
106 | #
107 |
108 | find "${wd}" -name \*.pyc -print0 | xargs -0 rm;
109 |
110 |
111 |
112 | #
113 | # Unit tests
114 | #
115 |
116 | cd "${wd}" && \
117 | "${wd}/bin/trial" \
118 | --temp-directory="${dev_home}/trial" \
119 | --rterrors \
120 | ${reactor} \
121 | ${random} \
122 | ${until_fail} \
123 | ${no_color} \
124 | ${coverage} \
125 | ${numjobs} \
126 | "$@";
127 |
128 |
129 | if ! "${lint}"; then
130 | exit 0;
131 | fi;
132 |
133 |
134 |
135 | #
136 | # Linting
137 | #
138 |
139 | echo "";
140 | echo "Running pyflakes...";
141 |
142 | "${python}" -m pip install pyflakes --upgrade >> "${dev_home}/setup.log";
143 | tmp="$(mktemp -t "twext_flakes.XXXXX")";
144 | cd "${wd}" && "${python}" -m pyflakes "$@" | tee "${tmp}" 2>&1;
145 | if [ -s "${tmp}" ]; then
146 | echo "**** Pyflakes says you have some code to clean up. ****";
147 | exit 1;
148 | fi;
149 | rm -f "${tmp}";
150 |
151 |
152 |
153 | #
154 | # Empty files
155 | #
156 |
157 | echo "";
158 | echo "Checking for empty files...";
159 | tmp="$(mktemp -t "twext_test_empty.XXXXX")";
160 |
161 | find "${wd}" \
162 | '!' '(' \
163 | -type d \
164 | '(' -path '*/.*' -or -name data -or -name build ')' \
165 | -prune \
166 | ')' \
167 | -type f -size 0 \
168 | > "${tmp}";
169 |
170 | if [ -s "${tmp}" ]; then
171 | echo "**** Empty files: ****";
172 | cat "${tmp}";
173 | exit 1;
174 | fi;
175 | rm -f "${tmp}";
176 |
--------------------------------------------------------------------------------
/bin/test_opendirectory_auth:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##
4 | # Copyright (c) 2006-2017 Apple Inc. All rights reserved.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | ##
18 |
19 | from __future__ import print_function
20 |
21 | import sys
22 |
23 | # PYTHONPATH
24 |
25 | if __name__ == "__main__":
26 | if "PYTHONPATH" in globals():
27 | sys.path.insert(0, PYTHONPATH)
28 | else:
29 | try:
30 | import _twext_preamble
31 | except ImportError:
32 | sys.exc_clear()
33 |
34 | from twext.who.opendirectory._scripts import run_auth
35 | run_auth()
36 |
--------------------------------------------------------------------------------
/bin/test_opendirectory_lookup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | ##
4 | # Copyright (c) 2006-2017 Apple Inc. All rights reserved.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | ##
18 |
19 | from __future__ import print_function
20 |
21 | import sys
22 |
23 | # PYTHONPATH
24 |
25 | if __name__ == "__main__":
26 | if "PYTHONPATH" in globals():
27 | sys.path.insert(0, PYTHONPATH)
28 | else:
29 | try:
30 | import _twext_preamble
31 | except ImportError:
32 | sys.exc_clear()
33 |
34 | from twext.who.opendirectory._scripts import run_lookup
35 | run_lookup()
36 |
--------------------------------------------------------------------------------
/bin/trial:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e
21 | set -u
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd)";
24 |
25 | . "${wd}/bin/_build.sh";
26 |
27 | init_build > /dev/null;
28 | do_setup="false";
29 | develop > /dev/null;
30 |
31 | exec "${python}" \
32 | -c "\
33 | import sys;\
34 | import os;\
35 | sys.path.insert(0, os.path.abspath(os.getcwd()));\
36 | from twisted.scripts.trial import run;\
37 | run()\
38 | " \
39 | "$@";
40 |
--------------------------------------------------------------------------------
/bin/twistd:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e
21 | set -u
22 |
23 | wd="$(cd "$(dirname "$0")/.." && pwd)";
24 |
25 | . "${wd}/bin/_build.sh";
26 |
27 | init_build > /dev/null;
28 |
29 | exec "${python}" \
30 | -c "\
31 | import sys;\
32 | import os;\
33 | sys.path.insert(0, os.path.abspath(os.getcwd()));\
34 | from twisted.scripts.twistd import run;\
35 | run()\
36 | " \
37 | "$@";
38 |
--------------------------------------------------------------------------------
/bin/update_copyrights:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # -*- sh-basic-offset: 2 -*-
3 |
4 | ##
5 | # Copyright (c) 2013-2016 Apple Inc. All rights reserved.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ##
19 |
20 | set -e;
21 | set -u;
22 |
23 | find_files () {
24 | where="$1"; shift;
25 |
26 | find "${where}" \
27 | ! \( \
28 | -type d \
29 | \( \
30 | -name .git -o \
31 | -name build -o \
32 | -name data -o \
33 | -name '_trial_temp*' \
34 | \) \
35 | -prune \
36 | \) \
37 | -type f \
38 | ! -name '.#*' \
39 | ! -name '#*#' \
40 | ! -name '*~' \
41 | ! -name '*.pyc' \
42 | ! -name '*.log' \
43 | ! -name update_copyrights \
44 | -print0;
45 | }
46 |
47 | wd="$(cd "$(dirname "$0")/.." && pwd)";
48 |
49 | this_year="$(date "+%Y")";
50 | last_year=$((${this_year} - 1));
51 |
52 | tmp="$(mktemp -t "$$")";
53 | find_files "${wd}" > "${tmp}";
54 |
55 | ff () { cat "${tmp}"; }
56 |
57 | echo "Updating copyrights from ${last_year} to ${this_year}...";
58 |
59 | ff | xargs -0 perl -i -pe 's|(Copyright \(c\) .*-)'"${last_year}"'( Apple)|${1}'"${this_year}"'${2}|';
60 | ff | xargs -0 perl -i -pe 's|(Copyright \(c\) )'"${last_year}"'( Apple)|${1}'"${last_year}-${this_year}"'${2}|';
61 |
62 | ff | xargs -0 grep -e 'Copyright (c) .* Apple' \
63 | | grep -v -e 'Copyright (c) .*'"${this_year}"' Apple' \
64 | ;
65 |
66 | rm "${tmp}";
67 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | # Requirements for development.
2 |
3 | # Get our own dependencies (from setup.py)
4 | -e .
5 |
6 | # Additional dependencies for development
7 | pyflakes
8 | docutils>=0.11
9 | mockldap>=0.1.4
10 |
--------------------------------------------------------------------------------
/twext/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to the Twisted Framework.
19 | """
20 |
21 | try:
22 | from twext.version import version as __version__
23 | except ImportError:
24 | __version__ = None
25 |
--------------------------------------------------------------------------------
/twext/application/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to L{twisted.application}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/application/service.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to L{twisted.application.service}.
19 | """
20 |
21 | __all__ = [
22 | "ReExecService",
23 | ]
24 |
25 | import sys
26 | import os
27 | import signal
28 |
29 | from twisted.application.service import MultiService
30 |
31 | from twext.python.log import Logger
32 |
33 |
34 | class ReExecService(MultiService):
35 | """
36 | A MultiService which catches SIGHUP and re-exec's the process.
37 | """
38 | log = Logger()
39 |
40 | def __init__(self, pidfilePath, reactor=None):
41 | """
42 | @param pidFilePath: Absolute path to the pidfile which will need to be
43 | removed
44 | @type pidFilePath: C{str}
45 | """
46 | self.pidfilePath = pidfilePath
47 | if reactor is None:
48 | from twisted.internet import reactor
49 | self.reactor = reactor
50 | MultiService.__init__(self)
51 |
52 | def reExec(self):
53 | """
54 | Removes pidfile, registers an exec to happen after shutdown, then
55 | stops the reactor.
56 | """
57 | self.log.warn("SIGHUP received - restarting")
58 | try:
59 | self.log.info("Removing pidfile: {log_source.pidfilePath}")
60 | os.remove(self.pidfilePath)
61 | except OSError:
62 | pass
63 | self.reactor.addSystemEventTrigger(
64 | "after", "shutdown", os.execv,
65 | sys.executable, [sys.executable] + sys.argv
66 | )
67 | self.reactor.stop()
68 |
69 | def sighupHandler(self, num, frame):
70 | self.reactor.callFromThread(self.reExec)
71 |
72 | def startService(self):
73 | self.previousHandler = signal.signal(signal.SIGHUP, self.sighupHandler)
74 | MultiService.startService(self)
75 |
76 | def stopService(self):
77 | signal.signal(signal.SIGHUP, self.previousHandler)
78 | MultiService.stopService(self)
79 |
--------------------------------------------------------------------------------
/twext/enterprise/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to L{twisted.enterprise};
19 | things related to database connectivity and management.
20 | """
21 |
--------------------------------------------------------------------------------
/twext/enterprise/dal/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Toolkit for building a Data-Access Layer (DAL).
19 |
20 | This includes an abstract representation of SQL objects like tables, columns,
21 | sequences and queries, a parser to convert your schema to that representation,
22 | and tools for working with it.
23 |
24 | In some ways this is similar to the low levels of something like SQLAlchemy, but
25 | it is designed to be more introspectable, to allow for features like automatic
26 | caching and index detection. NB: work in progress.
27 | """
28 |
--------------------------------------------------------------------------------
/twext/enterprise/dal/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.enterprise.dal}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/enterprise/jobs/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2015-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
--------------------------------------------------------------------------------
/twext/enterprise/jobs/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2015-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
--------------------------------------------------------------------------------
/twext/enterprise/jobs/utils.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2015-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | from twisted.internet.defer import inlineCallbacks, returnValue
18 | from twisted.python.failure import Failure
19 | from datetime import datetime
20 |
21 |
22 | @inlineCallbacks
23 | def inTransaction(transactionCreator, operation, label="jobqueue.inTransaction", **kwargs):
24 | """
25 | Perform the given operation in a transaction, committing or aborting as
26 | required.
27 |
28 | @param transactionCreator: a 0-arg callable that returns an
29 | L{IAsyncTransaction}
30 |
31 | @param operation: a 1-arg callable that takes an L{IAsyncTransaction} and
32 | returns a value.
33 |
34 | @param label: label to be used with the transaction.
35 |
36 | @return: a L{Deferred} that fires with C{operation}'s result or fails with
37 | its error, unless there is an error creating, aborting or committing
38 | the transaction.
39 | """
40 | txn = transactionCreator(label=label)
41 | try:
42 | result = yield operation(txn, **kwargs)
43 | except:
44 | f = Failure()
45 | yield txn.abort()
46 | returnValue(f)
47 | else:
48 | yield txn.commit()
49 | returnValue(result)
50 |
51 |
52 | def astimestamp(v):
53 | """
54 | Convert the given datetime to a POSIX timestamp.
55 | """
56 | return (v - datetime.utcfromtimestamp(0)).total_seconds()
57 |
--------------------------------------------------------------------------------
/twext/enterprise/locking.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.enterprise.test.test_locking -*-
2 | ##
3 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Utilities to restrict concurrency based on mutual exclusion.
20 | """
21 |
22 | from twext.enterprise.dal.model import Table
23 | from twext.enterprise.dal.model import SQLType
24 | from twext.enterprise.dal.model import Constraint
25 | from twext.enterprise.dal.syntax import SchemaSyntax
26 | from twext.enterprise.dal.model import Schema
27 | from twext.enterprise.dal.record import Record
28 | from twext.enterprise.dal.record import fromTable
29 |
30 |
31 | class AlreadyUnlocked(Exception):
32 | """
33 | The lock you were trying to unlock was already unlocked.
34 | """
35 |
36 |
37 | class LockTimeout(Exception):
38 | """
39 | The lock you were trying to lock was already locked causing a timeout.
40 | """
41 |
42 |
43 | def makeLockSchema(inSchema):
44 | """
45 | Create a self-contained schema just for L{Locker} use, in C{inSchema}.
46 |
47 | @param inSchema: a L{Schema} to add the locks table to.
48 | @type inSchema: L{Schema}
49 |
50 | @return: inSchema
51 | """
52 | LockTable = Table(inSchema, "NAMED_LOCK")
53 |
54 | LockTable.addColumn("LOCK_NAME", SQLType("varchar", 255))
55 | LockTable.tableConstraint(Constraint.NOT_NULL, ["LOCK_NAME"])
56 | LockTable.tableConstraint(Constraint.UNIQUE, ["LOCK_NAME"])
57 | LockTable.primaryKey = [LockTable.columnNamed("LOCK_NAME")]
58 |
59 | return inSchema
60 |
61 | LockSchema = SchemaSyntax(makeLockSchema(Schema(__file__)))
62 |
63 |
64 | class NamedLock(Record, fromTable(LockSchema.NAMED_LOCK)):
65 | """
66 | An L{AcquiredLock} lock against a shared data store that the current
67 | process holds via the referenced transaction.
68 | """
69 |
70 | @classmethod
71 | def acquire(cls, txn, name):
72 | """
73 | Acquire a lock with the given name.
74 |
75 | @param name: The name of the lock to acquire. Against the same store,
76 | no two locks may be acquired.
77 | @type name: L{unicode}
78 |
79 | @return: a L{Deferred} that fires with an L{AcquiredLock} when the lock
80 | has fired, or fails when the lock has not been acquired.
81 | """
82 |
83 | def autoRelease(self):
84 | txn.preCommit(lambda: self.release(True))
85 | return self
86 |
87 | def lockFailed(f):
88 | raise LockTimeout(name)
89 |
90 | d = cls.create(txn, lockName=name)
91 | d.addCallback(autoRelease)
92 | d.addErrback(lockFailed)
93 | return d
94 |
95 | def release(self, ignoreAlreadyUnlocked=False):
96 | """
97 | Release this lock.
98 |
99 | @param ignoreAlreadyUnlocked: If you don't care about the current
100 | status of this lock, and just want to release it if it is still
101 | acquired, pass this parameter as L{True}. Otherwise this method
102 | will raise an exception if it is invoked when the lock has already
103 | been released.
104 |
105 | @raise: L{AlreadyUnlocked}
106 |
107 | @return: A L{Deferred} that fires with L{None} when the lock has been
108 | unlocked.
109 | """
110 | return self.delete()
111 |
--------------------------------------------------------------------------------
/twext/enterprise/test/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | ##
3 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Tests for L{twext.enterprise}.
20 | """
21 |
--------------------------------------------------------------------------------
/twext/enterprise/test/test_fixtures.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.enterprise.fixtures}.
19 |
20 | Quis custodiet ipsos custodes? This module, that's who.
21 | """
22 |
23 | from twext.enterprise.fixtures import buildConnectionPool
24 |
25 | from twisted.trial.unittest import TestCase
26 | from twisted.trial.reporter import TestResult
27 | from twext.enterprise.adbapi2 import ConnectionPool
28 |
29 |
30 | class PoolTests(TestCase):
31 | """
32 | Tests for fixtures that create a connection pool.
33 | """
34 |
35 | def test_buildConnectionPool(self):
36 | """
37 | L{buildConnectionPool} returns a L{ConnectionPool} which will be
38 | running only for the duration of the test.
39 | """
40 | collect = []
41 |
42 | class SampleTest(TestCase):
43 |
44 | def setUp(self):
45 | self.pool = buildConnectionPool(self)
46 |
47 | def test_sample(self):
48 | collect.append(self.pool.running)
49 |
50 | def tearDown(self):
51 | collect.append(self.pool.running)
52 |
53 | r = TestResult()
54 | t = SampleTest("test_sample")
55 | t.run(r)
56 | self.assertIsInstance(t.pool, ConnectionPool)
57 | self.assertEqual([True, False], collect)
58 |
--------------------------------------------------------------------------------
/twext/enterprise/test/test_locking.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for mutual exclusion locks.
19 | """
20 |
21 | from twisted.internet.defer import inlineCallbacks
22 | from twisted.trial.unittest import TestCase
23 |
24 | from twext.enterprise.fixtures import buildConnectionPool
25 | from twext.enterprise.locking import NamedLock, LockTimeout
26 | from twext.enterprise.dal.syntax import Select
27 | from twext.enterprise.locking import LockSchema
28 |
29 | schemaText = """
30 | create table NAMED_LOCK (LOCK_NAME varchar(255) unique primary key);
31 | """
32 |
33 |
34 | class TestLocking(TestCase):
35 | """
36 | Test locking and unlocking a database row.
37 | """
38 |
39 | def setUp(self):
40 | """
41 | Build a connection pool for the tests to use.
42 | """
43 | self.pool = buildConnectionPool(self, schemaText)
44 |
45 | @inlineCallbacks
46 | def test_acquire(self):
47 | """
48 | Acquiring a lock adds a row in that transaction.
49 | """
50 | txn = self.pool.connection()
51 | yield NamedLock.acquire(txn, u"a test lock")
52 | rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn)
53 | self.assertEquals(rows, [tuple([u"a test lock"])])
54 |
55 | @inlineCallbacks
56 | def test_release(self):
57 | """
58 | Releasing an acquired lock removes the row.
59 | """
60 | txn = self.pool.connection()
61 | lck = yield NamedLock.acquire(txn, u"a test lock")
62 | yield lck.release()
63 | rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn)
64 | self.assertEquals(rows, [])
65 |
66 | @inlineCallbacks
67 | def test_autoRelease(self):
68 | """
69 | Committing a transaction automatically releases all of its locks.
70 | """
71 | txn = self.pool.connection()
72 | yield NamedLock.acquire(txn, u"something")
73 | yield txn.commit()
74 | txn2 = self.pool.connection()
75 | rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn2)
76 | self.assertEquals(rows, [])
77 |
78 | @inlineCallbacks
79 | def test_timeout(self):
80 | """
81 | Trying to acquire second lock times out.
82 | """
83 | txn1 = self.pool.connection()
84 | yield NamedLock.acquire(txn1, u"a test lock")
85 |
86 | txn2 = self.pool.connection()
87 | yield self.assertFailure(
88 | NamedLock.acquire(txn2, u"a test lock"), LockTimeout
89 | )
90 | yield txn2.abort()
91 | self.flushLoggedErrors()
92 |
--------------------------------------------------------------------------------
/twext/enterprise/test/test_util.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | import datetime
18 |
19 | from twisted.trial.unittest import TestCase
20 |
21 | from twext.enterprise.util import parseSQLTimestamp
22 |
23 |
24 | class TimestampTests(TestCase):
25 | """
26 | Tests for date-related functions.
27 | """
28 |
29 | def test_parseSQLTimestamp(self):
30 | """
31 | L{parseSQLTimestamp} parses the traditional SQL timestamp.
32 | """
33 | tests = (
34 | ("2012-04-04 12:34:56", datetime.datetime(2012, 4, 4, 12, 34, 56)),
35 | ("2012-12-31 01:01:01", datetime.datetime(2012, 12, 31, 1, 1, 1)),
36 | )
37 |
38 | for sqlStr, result in tests:
39 | self.assertEqual(parseSQLTimestamp(sqlStr), result)
40 |
--------------------------------------------------------------------------------
/twext/enterprise/util.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.enterprise.test.test_util -*-
2 | ##
3 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Utilities for dealing with different databases.
20 | """
21 |
22 | from datetime import datetime
23 |
24 | SQL_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
25 |
26 |
27 | def parseSQLTimestamp(ts):
28 | """
29 | Parse an SQL timestamp string.
30 | """
31 | # Handle case where fraction seconds may not be present
32 | if not isinstance(ts, datetime):
33 | if len(ts) < len(SQL_TIMESTAMP_FORMAT):
34 | ts += ".0"
35 | return datetime.strptime(ts, SQL_TIMESTAMP_FORMAT)
36 | else:
37 | return ts
38 |
39 |
40 | def mapOracleOutputType(column):
41 | """
42 | Map a single output value from cx_Oracle based on some rules and
43 | expectations that we have based on the pgdb bindings.
44 |
45 | @param column: a single value from a column.
46 |
47 | @return: a converted value based on the type of the input; oracle CLOBs and
48 | datetime timestamps will be converted to strings, unicode values will
49 | be converted to UTF-8 encoded byte sequences (C{str}s), and floating
50 | point numbers will be converted to integer types if they are integers.
51 | Any other types will be left alone.
52 | """
53 | if hasattr(column, "read"):
54 | # Try to detect large objects and format convert them to
55 | # strings on the fly. We need to do this as we read each
56 | # row, due to the issue described here -
57 | # http://cx-oracle.sourceforge.net/html/lob.html - in
58 | # particular, the part where it says "In particular, do not
59 | # use the fetchall() method".
60 | column = column.read()
61 |
62 | elif isinstance(column, float):
63 | # cx_Oracle maps _all_ numbers to float types, which is more
64 | # consistent, but we expect the database to be able to store integers
65 | # as integers (in fact almost all the values in our schema are
66 | # integers), so we map those values which exactly match back into
67 | # integers.
68 | if int(column) == column:
69 | return int(column)
70 | else:
71 | return column
72 |
73 | if isinstance(column, unicode):
74 | # Finally, we process all data as UTF-8 byte strings in order to reduce
75 | # memory consumption. Pass any unicode string values back to the
76 | # application as unicode.
77 | column = column.encode("utf-8")
78 |
79 | return column
80 |
--------------------------------------------------------------------------------
/twext/internet/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to L{twisted.internet}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/internet/adaptendpoint.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.internet.test.test_adaptendpoint -*-
2 | ##
3 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Adapter for old-style connectTCP/connectSSL code to use endpoints and be happy;
20 | specifically, to receive the additional duplicate notifications that it wants to
21 | receive, L{clientConnectionLost} and L{clientConnectionFailed} on the factory.
22 | """
23 |
24 | from zope.interface import implements
25 |
26 | from twisted.internet.interfaces import IConnector
27 |
28 | from twisted.internet.protocol import Factory
29 | from twisted.python import log
30 |
31 | __all__ = [
32 | "connect"
33 | ]
34 |
35 |
36 | class _WrappedProtocol(object):
37 | """
38 | A protocol providing a thin wrapper that relays the connectionLost
39 | notification.
40 | """
41 |
42 | def __init__(self, wrapped, wrapper):
43 | """
44 | @param wrapped: the wrapped L{IProtocol} provider, to which all methods
45 | will be relayed.
46 |
47 | @param wrapper: The L{LegacyClientFactoryWrapper} that holds the
48 | relevant L{ClientFactory}.
49 | """
50 | self._wrapped = wrapped
51 | self._wrapper = wrapper
52 |
53 | def __getattr__(self, attr):
54 | """
55 | Relay all undefined methods to the wrapped protocol.
56 | """
57 | return getattr(self._wrapped, attr)
58 |
59 | def connectionLost(self, reason):
60 | """
61 | When the connection is lost, return the connection.
62 | """
63 | try:
64 | self._wrapped.connectionLost(reason)
65 | except:
66 | log.err()
67 | self._wrapper.legacyFactory.clientConnectionLost(self._wrapper, reason)
68 |
69 |
70 | class LegacyClientFactoryWrapper(Factory):
71 | implements(IConnector)
72 |
73 | def __init__(self, legacyFactory, endpoint):
74 | self.currentlyConnecting = False
75 | self.legacyFactory = legacyFactory
76 | self.endpoint = endpoint
77 | self._connectedProtocol = None
78 | self._outstandingAttempt = None
79 |
80 | def getDestination(self):
81 | """
82 | Implement L{IConnector.getDestination}.
83 |
84 | @return: the endpoint being connected to as the destination.
85 | """
86 | return self.endpoint
87 |
88 | def buildProtocol(self, addr):
89 | """
90 | Implement L{Factory.buildProtocol} to return a wrapper protocol that
91 | will capture C{connectionLost} notifications.
92 |
93 | @return: a L{Protocol}.
94 | """
95 | return _WrappedProtocol(self.legacyFactory.buildProtocol(addr), self)
96 |
97 | def connect(self):
98 | """
99 | Implement L{IConnector.connect} to connect the endpoint.
100 | """
101 | if self._outstandingAttempt is not None:
102 | raise RuntimeError("connection already in progress")
103 | self.legacyFactory.startedConnecting(self)
104 | d = self._outstandingAttempt = self.endpoint.connect(self)
105 |
106 | @d.addBoth
107 | def attemptDone(result):
108 | self._outstandingAttempt = None
109 | return result
110 |
111 | def rememberProto(proto):
112 | self._connectedProtocol = proto
113 | return proto
114 |
115 | def callClientConnectionFailed(reason):
116 | self.legacyFactory.clientConnectionFailed(self, reason)
117 | d.addCallbacks(rememberProto, callClientConnectionFailed)
118 |
119 | def disconnect(self):
120 | """
121 | Implement L{IConnector.disconnect}.
122 | """
123 | if self._connectedProtocol is not None:
124 | self._connectedProtocol.transport.loseConnection()
125 | elif self._outstandingAttempt is not None:
126 | self._outstandingAttempt.cancel()
127 |
128 | def stopConnecting(self):
129 | """
130 | Implement L{IConnector.stopConnecting}.
131 | """
132 | if self._outstandingAttempt is None:
133 | raise RuntimeError("no connection attempt in progress")
134 | self.disconnect()
135 |
136 |
137 | def connect(endpoint, clientFactory):
138 | """
139 | Connect a L{twisted.internet.protocol.ClientFactory} to a remote host using
140 | the given L{twisted.internet.interfaces.IStreamClientEndpoint}. This relays
141 | C{clientConnectionFailed} and C{clientConnectionLost} notifications as
142 | legacy code using the L{ClientFactory} interface, such as,
143 | L{ReconnectingClientFactory} would expect.
144 |
145 | @param endpoint: The endpoint to connect to.
146 | @type endpoint: L{twisted.internet.interfaces.IStreamClientEndpoint}
147 |
148 | @param clientFactory: The client factory doing the connecting.
149 | @type clientFactory: L{twisted.internet.protocol.ClientFactory}
150 |
151 | @return: A connector object representing the connection attempt just
152 | initiated.
153 | @rtype: L{IConnector}
154 | """
155 | wrap = LegacyClientFactoryWrapper(clientFactory, endpoint)
156 | wrap.noisy = clientFactory.noisy # relay the noisy attribute to the wrapper
157 | wrap.connect()
158 | return wrap
159 |
--------------------------------------------------------------------------------
/twext/internet/decorate.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Decorators.
19 | """
20 |
21 | __all__ = [
22 | "memoizedKey",
23 | ]
24 |
25 |
26 | from inspect import getargspec
27 |
28 | from twisted.internet.defer import Deferred, succeed
29 |
30 |
31 | class Memoizable(object):
32 | """
33 | A class that stores itself in the memo dictionary.
34 | """
35 |
36 | def memoMe(self, key, memo):
37 | """
38 | Add this object to the memo dictionary in whatever fashion is appropriate.
39 |
40 | @param key: key used for lookup
41 | @type key: C{object} (typically C{str} or C{int})
42 | @param memo: the dict to store to
43 | @type memo: C{dict}
44 | """
45 | raise NotImplementedError
46 |
47 |
48 | def memoizedKey(keyArgument, memoAttribute, deferredResult=True):
49 | """
50 | Decorator which memoizes the result of a method on that method's instance. If the instance is derived from
51 | class Memoizable, then the memoMe method is used to store the result, otherwise it is stored directly in
52 | the dict.
53 |
54 | @param keyArgument: The name of the "key" argument.
55 | @type keyArgument: C{str}
56 |
57 | @param memoAttribute: The name of the attribute on the instance which
58 | should be used for memoizing the result of this method; the attribute
59 | itself must be a dictionary. Alternately, if the specified argument is
60 | callable, it is a callable that takes the arguments passed to the
61 | decorated method and returns the memo dictionaries.
62 | @type memoAttribute: C{str} or C{callable}
63 |
64 | @param deferredResult: Whether the result must be a deferred.
65 | """
66 | def getarg(argname, argspec, args, kw):
67 | """
68 | Get an argument from some arguments.
69 |
70 | @param argname: The name of the argument to retrieve.
71 |
72 | @param argspec: The result of L{inspect.getargspec}.
73 |
74 | @param args: positional arguments passed to the function specified by
75 | argspec.
76 |
77 | @param kw: keyword arguments passed to the function specified by
78 | argspec.
79 |
80 | @return: The value of the argument named by 'argname'.
81 | """
82 | argnames = argspec[0]
83 | try:
84 | argpos = argnames.index(argname)
85 | except ValueError:
86 | argpos = None
87 | if argpos is not None:
88 | if len(args) > argpos:
89 | return args[argpos]
90 | if argname in kw:
91 | return kw[argname]
92 | else:
93 | raise TypeError("could not find key argument %r in %r/%r (%r)" % (
94 | argname, args, kw, argpos
95 | ))
96 |
97 | def decorate(thunk):
98 | # cheater move to try to get the right argspec from inlineCallbacks.
99 | # This could probably be more robust, but the 'cell_contents' thing
100 | # probably can't (that's the only real reference to the underlying
101 | # function).
102 | if thunk.func_code.co_name == "unwindGenerator":
103 | specTarget = thunk.func_closure[0].cell_contents
104 | else:
105 | specTarget = thunk
106 | spec = getargspec(specTarget)
107 |
108 | def outer(*a, **kw):
109 | self = a[0]
110 | if callable(memoAttribute):
111 | memo = memoAttribute(*a, **kw)
112 | else:
113 | memo = getattr(self, memoAttribute)
114 | key = getarg(keyArgument, spec, a, kw)
115 | if key in memo:
116 | memoed = memo[key]
117 | if deferredResult:
118 | return succeed(memoed)
119 | else:
120 | return memoed
121 | result = thunk(*a, **kw)
122 |
123 | if isinstance(result, Deferred):
124 | def memoResult(finalResult):
125 | if isinstance(finalResult, Memoizable):
126 | finalResult.memoMe(key, memo)
127 | elif finalResult is not None:
128 | memo[key] = finalResult
129 | return finalResult
130 | result.addCallback(memoResult)
131 | elif result is not None:
132 | memo[key] = result
133 | return result
134 |
135 | return outer
136 |
137 | return decorate
138 |
--------------------------------------------------------------------------------
/twext/internet/fswatch.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Watch the availablity of a file system directory
19 | """
20 |
21 | import os
22 | from zope.interface import Interface
23 | from twisted.internet import reactor
24 | from twisted.python.log import Logger
25 |
26 | try:
27 | from select import (
28 | kevent, KQ_FILTER_VNODE, KQ_EV_ADD, KQ_EV_ENABLE,
29 | KQ_EV_CLEAR, KQ_NOTE_DELETE, KQ_NOTE_RENAME, KQ_EV_EOF
30 | )
31 | kqueueSupported = True
32 | except ImportError:
33 | # kqueue not supported on this platform
34 | kqueueSupported = False
35 |
36 |
37 | class IDirectoryChangeListenee(Interface):
38 | """
39 | A delegate of DirectoryChangeListener
40 | """
41 |
42 | def disconnected():
43 | """
44 | The directory has been unmounted
45 | """
46 |
47 | def deleted():
48 | """
49 | The directory has been deleted
50 | """
51 |
52 | def renamed():
53 | """
54 | The directory has been renamed
55 | """
56 |
57 | def connectionLost(reason):
58 | """
59 | The file descriptor has been closed
60 | """
61 |
62 |
63 | # TODO: better way to tell if reactor is kqueue or not
64 | if kqueueSupported and hasattr(reactor, "_doWriteOrRead"):
65 |
66 | def patchReactor(reactor):
67 | # Wrap _doWriteOrRead to support KQ_FILTER_VNODE
68 | origDoWriteOrRead = reactor._doWriteOrRead
69 |
70 | def _doWriteOrReadOrVNodeEvent(selectable, fd, event):
71 | origDoWriteOrRead(selectable, fd, event)
72 | if event.filter == KQ_FILTER_VNODE:
73 | selectable.vnodeEventHappened(event)
74 |
75 | reactor._doWriteOrRead = _doWriteOrReadOrVNodeEvent
76 |
77 | patchReactor(reactor)
78 |
79 | class DirectoryChangeListener(Logger, object):
80 | """
81 | Listens for the removal, renaming, or general unavailability of a
82 | given directory, and lets a delegate listenee know about them.
83 | """
84 |
85 | def __init__(self, reactor, dirname, listenee):
86 | """
87 | @param reactor: the reactor
88 | @param dirname: the full path to the directory to watch; it must
89 | already exist
90 | @param listenee: the delegate to call
91 | @type listenee: IDirectoryChangeListenee
92 | """
93 | self._reactor = reactor
94 | self._fd = os.open(dirname, os.O_RDONLY)
95 | self._dirname = dirname
96 | self._listenee = listenee
97 |
98 | def logPrefix(self):
99 | return repr(self._dirname)
100 |
101 | def fileno(self):
102 | return self._fd
103 |
104 | def vnodeEventHappened(self, evt):
105 | if evt.flags & KQ_EV_EOF:
106 | self._listenee.disconnected()
107 | if evt.fflags & KQ_NOTE_DELETE:
108 | self._listenee.deleted()
109 | if evt.fflags & KQ_NOTE_RENAME:
110 | self._listenee.renamed()
111 |
112 | def startListening(self):
113 | ke = kevent(self._fd, filter=KQ_FILTER_VNODE,
114 | flags=(KQ_EV_ADD | KQ_EV_ENABLE | KQ_EV_CLEAR),
115 | fflags=KQ_NOTE_DELETE | KQ_NOTE_RENAME)
116 | self._reactor._kq.control([ke], 0, None)
117 | self._reactor._selectables[self._fd] = self
118 |
119 | def connectionLost(self, reason):
120 | os.close(self._fd)
121 | self._listenee.connectionLost(reason)
122 |
123 |
124 | else:
125 |
126 | # TODO: implement this for systems without kqueue support:
127 |
128 | class DirectoryChangeListener(Logger, object):
129 | """
130 | Listens for the removal, renaming, or general unavailability of a
131 | given directory, and lets a delegate listenee know about them.
132 | """
133 |
134 | def __init__(self, reactor, dirname, listenee):
135 | """
136 | @param reactor: the reactor
137 | @param dirname: the full path to the directory to watch; it must
138 | already exist
139 | @param listenee: the delegate to call
140 | @type listenee: IDirectoryChangeListenee
141 | """
142 | self._reactor = reactor
143 | self._fd = os.open(dirname, os.O_RDONLY)
144 | self._dirname = dirname
145 | self._listenee = listenee
146 |
147 | def logPrefix(self):
148 | return repr(self._dirname)
149 |
150 | def fileno(self):
151 | return self._fd
152 |
153 | def vnodeEventHappened(self, evt):
154 | pass
155 |
156 | def startListening(self):
157 | pass
158 |
159 | def connectionLost(self, reason):
160 | os.close(self._fd)
161 | self._listenee.connectionLost(reason)
162 |
--------------------------------------------------------------------------------
/twext/internet/gaiendpoint.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.internet.test.test_gaiendpoint -*-
2 | ##
3 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 | from __future__ import print_function
18 |
19 | """
20 | L{getaddrinfo}()-based endpoint
21 | """
22 |
23 | from socket import getaddrinfo, AF_UNSPEC, AF_INET, AF_INET6, SOCK_STREAM
24 | from twisted.internet.endpoints import TCP4ClientEndpoint, SSL4ClientEndpoint
25 | from twisted.internet.defer import Deferred
26 | from twisted.internet.threads import deferToThread
27 | from twisted.internet.task import LoopingCall
28 |
29 |
30 | class MultiFailure(Exception):
31 |
32 | def __init__(self, failures):
33 | super(MultiFailure, self).__init__("Failure with multiple causes.")
34 | self.failures = failures
35 |
36 |
37 | class GAIEndpoint(object):
38 | """
39 | Client endpoint that will call L{getaddrinfo} in a thread and then attempt
40 | to connect to each endpoint (almost) in parallel.
41 |
42 | @ivar reactor: The reactor to attempt the connection with.
43 | @type reactor: provider of L{IReactorTCP} and L{IReactorTime}
44 |
45 | @ivar host: The host to resolve.
46 | @type host: L{str}
47 |
48 | @ivar port: The port number to resolve.
49 | @type port: L{int}
50 |
51 | @ivar deferToThread: A function like L{deferToThread}, used to invoke
52 | getaddrinfo. (Replaceable mainly for testing purposes.)
53 | """
54 |
55 | deferToThread = staticmethod(deferToThread)
56 |
57 | def subEndpoint(self, reactor, host, port, contextFactory):
58 | """
59 | Create an endpoint to connect to based on a single address result from
60 | L{getaddrinfo}.
61 |
62 | @param reactor: the reactor to connect to
63 | @type reactor: L{IReactorTCP}
64 |
65 | @param host: The IP address of the host to connect to, in presentation
66 | format.
67 | @type host: L{str}
68 |
69 | @param port: The numeric port number to connect to.
70 | @type port: L{int}
71 |
72 | @param contextFactory: If not L{None}, the OpenSSL context factory to
73 | use to produce client connections.
74 |
75 | @return: a stream client endpoint that will connect to the given host
76 | and port via the given reactor.
77 | @rtype: L{IStreamClientEndpoint}
78 | """
79 | if contextFactory is None:
80 | return TCP4ClientEndpoint(reactor, host, port)
81 | else:
82 | return SSL4ClientEndpoint(reactor, host, port, contextFactory)
83 |
84 | def __init__(self, reactor, host, port, contextFactory=None):
85 | self.reactor = reactor
86 | self.host = host
87 | self.port = port
88 | self.contextFactory = contextFactory
89 |
90 | def connect(self, factory):
91 | dgai = self.deferToThread(getaddrinfo, self.host, self.port,
92 | AF_UNSPEC, SOCK_STREAM)
93 |
94 | @dgai.addCallback
95 | def gaiToEndpoints(gairesult):
96 | for family, _ignore_socktype, _ignore_proto, _ignore_canonname, sockaddr in gairesult:
97 | if family in [AF_INET6, AF_INET]:
98 | yield self.subEndpoint(self.reactor, sockaddr[0],
99 | sockaddr[1], self.contextFactory)
100 |
101 | @gaiToEndpoints.addCallback
102 | def connectTheEndpoints(endpoints):
103 | doneTrying = []
104 | outstanding = []
105 | errors = []
106 | succeeded = []
107 | actuallyDidIt = Deferred()
108 |
109 | def removeMe(result, attempt):
110 | outstanding.remove(attempt)
111 | return result
112 |
113 | def connectingDone(result):
114 | if lc.running:
115 | lc.stop()
116 | succeeded.append(True)
117 | for o in outstanding[::]:
118 | o.cancel()
119 | actuallyDidIt.callback(result)
120 | return None
121 |
122 | def lastChance():
123 | if doneTrying and not outstanding and not succeeded:
124 | # We've issued our last attempts. There are no remaining
125 | # outstanding attempts; they've all failed. We haven't
126 | # succeeded. Time... to die.
127 | actuallyDidIt.errback(MultiFailure(errors))
128 |
129 | def connectingFailed(why):
130 | errors.append(why)
131 | lastChance()
132 | return None
133 |
134 | def nextOne():
135 | try:
136 | endpoint = endpoints.next()
137 | except StopIteration:
138 | # Out of endpoints to try! Now it's time to wait for all of
139 | # the outstanding attempts to complete, and, if none of them
140 | # have been successful, then to give up with a relevant
141 | # error. They'll all be dealt with by connectingDone or
142 | # connectingFailed.
143 | doneTrying.append(True)
144 | lc.stop()
145 | lastChance()
146 | else:
147 | attempt = endpoint.connect(factory)
148 | attempt.addBoth(removeMe, attempt)
149 | attempt.addCallbacks(connectingDone, connectingFailed)
150 | outstanding.append(attempt)
151 | lc = LoopingCall(nextOne)
152 | lc.clock = self.reactor
153 | lc.start(0.0)
154 | return actuallyDidIt
155 | return dgai
156 |
157 |
158 | if __name__ == '__main__':
159 | from twisted.internet import reactor
160 | import sys
161 | if sys.argv[1:]:
162 | host = sys.argv[1]
163 | port = int(sys.argv[2])
164 | else:
165 | host = "localhost"
166 | port = 22
167 | gaie = GAIEndpoint(reactor, host, port)
168 |
169 | from twisted.internet.protocol import Factory, Protocol
170 |
171 | class HelloGoobye(Protocol, object):
172 |
173 | def connectionMade(self):
174 | print('Hello!')
175 | self.transport.loseConnection()
176 |
177 | def connectionLost(self, reason):
178 | print('Goodbye')
179 |
180 | class MyFactory(Factory, object):
181 |
182 | def buildProtocol(self, addr):
183 | print('Building protocol for:', addr)
184 | return HelloGoobye()
185 |
186 | def bye(what):
187 | print('bye', what)
188 | reactor.stop()
189 |
190 | gaie.connect(MyFactory()).addBoth(bye)
191 | reactor.run()
192 |
--------------------------------------------------------------------------------
/twext/internet/socketfile.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2015-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Socket file implementation for MaxAccept
19 | """
20 |
21 | __all__ = [
22 | "MaxAcceptSocketFileServer",
23 | ]
24 |
25 |
26 | from twisted.application import service
27 | from twisted.internet import endpoints
28 | from twisted.internet.defer import inlineCallbacks
29 |
30 | from twext.python.log import Logger
31 |
32 | log = Logger()
33 |
34 |
35 | def maxAcceptDoRead(self):
36 | self.numberAccepts = min(
37 | self.factory.maxRequests - self.factory.outstandingRequests,
38 | self.factory.maxAccepts
39 | )
40 | self.realDoRead()
41 |
42 |
43 | class MaxAcceptSocketFileServer(service.Service):
44 | """
45 | Socket File server
46 |
47 | @ivar myPort: When running, this is set to the L{IListeningPort} being
48 | managed by this service.
49 | """
50 |
51 | def __init__(self, protocolFactory, address, backlog=None):
52 | self.protocolFactory = protocolFactory
53 | self.protocolFactory.myServer = self
54 | self.address = address
55 | self.backlog = backlog
56 | self.myPort = None
57 |
58 | @inlineCallbacks
59 | def startService(self):
60 | from twisted.internet import reactor
61 | endpoint = endpoints.UNIXServerEndpoint(
62 | reactor, self.address, backlog=self.backlog, wantPID=1
63 | )
64 | self.myPort = yield endpoint.listen(self.protocolFactory)
65 |
66 | # intercept doRead() to set numberAccepts
67 | self.myPort.realDoRead = self.myPort.doRead
68 | self.myPort.doRead = maxAcceptDoRead.__get__(
69 | self.myPort, self.myPort.__class__
70 | )
71 |
72 | @inlineCallbacks
73 | def stopService(self):
74 | """
75 | Wait for outstanding requests to finish
76 |
77 | @return: a Deferred which fires when all outstanding requests are
78 | complete
79 | """
80 | if self.myPort is not None:
81 | yield self.myPort.stopListening()
82 |
83 | if hasattr(self.protocolFactory, "allConnectionsClosed"):
84 | yield self.protocolFactory.allConnectionsClosed()
85 |
--------------------------------------------------------------------------------
/twext/internet/spawnsvc.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2011-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Utility service that can spawn subprocesses.
19 | """
20 |
21 | import os
22 | import sys
23 |
24 | from twisted.python import log
25 |
26 | from twisted.python.reflect import namedAny
27 | from twisted.internet.stdio import StandardIO
28 | from twisted.internet.error import ReactorNotRunning
29 |
30 | if __name__ == '__main__':
31 |
32 | sys.stdout = sys.stderr
33 | there = sys.argv[1]
34 | protocolClass = namedAny(there)
35 | proto = protocolClass()
36 | origLost = proto.connectionLost
37 |
38 | def goodbye(reason):
39 | """
40 | Stop the process if stdin is closed.
41 | """
42 | try:
43 | reactor.stop()
44 | except ReactorNotRunning:
45 | pass
46 | return origLost(reason)
47 | proto.connectionLost = goodbye
48 | StandardIO(proto)
49 | from twisted.internet import reactor
50 | reactor.run()
51 | os._exit(0)
52 |
53 |
54 | from zope.interface import implements
55 |
56 | from twisted.internet.interfaces import ITransport, IPushProducer, IConsumer
57 |
58 | from twisted.application.service import Service
59 | from twisted.python.reflect import qual
60 | from twisted.internet.protocol import ProcessProtocol
61 | from twisted.internet.defer import Deferred, succeed
62 |
63 |
64 | class BridgeTransport(object):
65 | """
66 | ITransport implementation for the protocol in the parent process running a
67 | L{SpawnerService}.
68 | """
69 |
70 | implements(ITransport, IPushProducer, IConsumer)
71 |
72 | def __init__(self, processTransport):
73 | """
74 | Create this bridge transport connected to an L{IProcessTransport}.
75 | """
76 | self.transport = processTransport
77 |
78 | def __getattr__(self, name):
79 | """
80 | Delegate all attribute accesses to the process traansport.
81 | """
82 | return getattr(self.transport, name)
83 |
84 | def getPeer(self):
85 | """
86 | Get a fake peer address indicating the subprocess's pid.
87 | """
88 | return "Peer:PID:" + str(self.transport.pid)
89 |
90 | def getHost(self):
91 | """
92 | Get a fake host address indicating the subprocess's pid.
93 | """
94 | return "Host:PID:" + str(self.transport.pid)
95 |
96 |
97 | class BridgeProtocol(ProcessProtocol, object):
98 | """
99 | Process protocol implementation that delivers data to the C{hereProto}
100 | associated with an invocation of L{SpawnerService.spawn}.
101 |
102 | @ivar service: a L{SpawnerService} that created this L{BridgeProtocol}
103 |
104 | @ivar protocol: a reference to the L{IProtocol}.
105 |
106 | @ivar killTimeout: number of seconds after sending SIGINT that this process
107 | will send SIGKILL.
108 | """
109 |
110 | def __init__(self, service, protocol, killTimeout=15.0):
111 | self.service = service
112 | self.protocol = protocol
113 | self.killTimeout = killTimeout
114 | self.service.addBridge(self)
115 |
116 | def connectionMade(self):
117 | """
118 | The subprocess was started.
119 | """
120 | self.protocol.makeConnection(BridgeTransport(self.transport))
121 |
122 | def outReceived(self, data):
123 | """
124 | Some data was received to standard output; relay it to the protocol.
125 | """
126 | self.protocol.dataReceived(data)
127 |
128 | def errReceived(self, data):
129 | """
130 | Some standard error was received from the subprocess.
131 | """
132 | log.msg("Error output from process: " + data,
133 | isError=True)
134 |
135 | _killTimeout = None
136 |
137 | def eventuallyStop(self):
138 | """
139 | Eventually stop this subprocess. Send it a SIGTERM, and if it hasn't
140 | stopped by C{self.killTimeout} seconds, send it a SIGKILL.
141 | """
142 | self.transport.signalProcess('TERM')
143 |
144 | def reallyStop():
145 | self.transport.signalProcess("KILL")
146 | self._killTimeout = None
147 | self._killTimeout = (
148 | self.service.reactor.callLater(self.killTimeout, reallyStop)
149 | )
150 |
151 | def processEnded(self, reason):
152 | """
153 | The process has ended; notify the L{SpawnerService} that this bridge
154 | has stopped.
155 | """
156 | if self._killTimeout is not None:
157 | self._killTimeout.cancel()
158 | self.protocol.connectionLost(reason)
159 | self.service.removeBridge(self)
160 |
161 |
162 | class SpawnerService(Service, object):
163 | """
164 | Process to spawn services and then shut them down.
165 |
166 | @ivar reactor: an L{IReactorProcess}/L{IReactorTime}
167 |
168 | @ivar pendingSpawns: a C{list} of 2-C{tuple}s of hereProto, thereProto.
169 |
170 | @ivar bridges: a C{list} of L{BridgeProtocol} instances.
171 | """
172 |
173 | def __init__(self, reactor=None):
174 | if reactor is None:
175 | from twisted.internet import reactor
176 | self.reactor = reactor
177 | self.pendingSpawns = []
178 | self.bridges = []
179 | self._stopAllDeferred = None
180 |
181 | def spawn(self, hereProto, thereProto, childFDs=None):
182 | """
183 | Spawn a subprocess with a connected pair of protocol objects, one in
184 | the current process, one in the subprocess.
185 |
186 | @param hereProto: a L{Protocol} instance to listen in this process.
187 |
188 | @param thereProto: a top-level class or function that will be imported
189 | and called in the spawned subprocess.
190 |
191 | @param childFDs: File descriptors to share with the subprocess; same
192 | format as L{IReactorProcess.spawnProcess}.
193 |
194 | @return: a L{Deferred} that fires when C{hereProto} is ready.
195 | """
196 | if not self.running:
197 | self.pendingSpawns.append((hereProto, thereProto))
198 | return
199 | name = qual(thereProto)
200 | argv = [sys.executable, '-u', '-m', __name__, name]
201 | self.reactor.spawnProcess(
202 | BridgeProtocol(self, hereProto), sys.executable,
203 | argv, os.environ, childFDs=childFDs
204 | )
205 | return succeed(hereProto)
206 |
207 | def startService(self):
208 | """
209 | Start the service; spawn any processes previously started with spawn().
210 | """
211 | super(SpawnerService, self).startService()
212 | for spawn in self.pendingSpawns:
213 | self.spawn(*spawn)
214 | self.pendingSpawns = []
215 |
216 | def addBridge(self, bridge):
217 | """
218 | Add a L{BridgeProtocol} to the list to be tracked.
219 | """
220 | self.bridges.append(bridge)
221 |
222 | def removeBridge(self, bridge):
223 | """
224 | The process controlled by a L{BridgeProtocol} has terminated; remove it
225 | from the active list, and fire any outstanding Deferred.
226 |
227 | @param bridge: the protocol which has ended.
228 | """
229 | self.bridges.remove(bridge)
230 | if self._stopAllDeferred is not None:
231 | if len(self.bridges) == 0:
232 | self._stopAllDeferred.callback(None)
233 | self._stopAllDeferred = None
234 |
235 | def stopService(self):
236 | """
237 | Stop the service.
238 | """
239 | super(SpawnerService, self).stopService()
240 | if self.bridges:
241 | self._stopAllDeferred = Deferred()
242 | for bridge in self.bridges:
243 | bridge.eventuallyStop()
244 | return self._stopAllDeferred
245 | return succeed(None)
246 |
--------------------------------------------------------------------------------
/twext/internet/ssl.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to twisted.internet.ssl.
19 | """
20 |
21 | __all__ = [
22 | "ChainingOpenSSLContextFactory",
23 | "simpleClientContextFactory",
24 | ]
25 |
26 | import OpenSSL
27 | from OpenSSL.SSL import Context as SSLContext, SSLv23_METHOD, OP_NO_SSLv2, \
28 | OP_CIPHER_SERVER_PREFERENCE, OP_NO_SSLv3, VERIFY_NONE, VERIFY_PEER, \
29 | VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE
30 |
31 | from twisted.internet.ssl import DefaultOpenSSLContextFactory
32 | from twisted.internet._sslverify import Certificate, _tolerateErrors, VerificationError, verifyHostname
33 | from twisted.python.failure import Failure
34 |
35 | import uuid
36 |
37 |
38 | _OP_NO_COMPRESSION = getattr(OpenSSL.SSL, 'OP_NO_COMPRESSION', 0x00020000)
39 | SSL_CB_HANDSHAKE_DONE = 0x20
40 |
41 |
42 | class ChainingOpenSSLContextFactory (DefaultOpenSSLContextFactory):
43 |
44 | def __init__(
45 | self, privateKeyFileName, certificateFileName,
46 | sslmethod=SSLv23_METHOD,
47 | certificateChainFile=None, keychainIdentity=None,
48 | passwdCallback=None, ciphers=None,
49 | verifyClient=False, requireClientCertificate=False,
50 | verifyClientOnce=True, verifyClientDepth=9,
51 | clientCACertFileNames=[], sendCAsToClient=True,
52 | peerName=None
53 | ):
54 | self.certificateChainFile = certificateChainFile
55 | self.keychainIdentity = keychainIdentity
56 | self.passwdCallback = passwdCallback
57 | self.ciphers = ciphers
58 |
59 | self.peerName = peerName
60 |
61 | self.verifyClient = verifyClient
62 | self.requireClientCertificate = requireClientCertificate
63 | self.verifyClientOnce = verifyClientOnce
64 | self.verifyClientDepth = verifyClientDepth
65 | self.clientCACertFileNames = clientCACertFileNames
66 | self.sendCAsToClient = sendCAsToClient
67 |
68 | DefaultOpenSSLContextFactory.__init__(
69 | self,
70 | privateKeyFileName,
71 | certificateFileName,
72 | sslmethod=sslmethod
73 | )
74 |
75 | def cacheContext(self):
76 | # Unfortunate code duplication.
77 | ctx = SSLContext(self.sslmethod)
78 |
79 | # Always disable SSLv2/SSLv3/Compression
80 | ctx.set_options(OP_NO_SSLv2)
81 | ctx.set_options(OP_NO_SSLv3)
82 | ctx.set_options(_OP_NO_COMPRESSION)
83 |
84 | if self.ciphers is not None:
85 | ctx.set_cipher_list(self.ciphers)
86 | ctx.set_options(OP_CIPHER_SERVER_PREFERENCE)
87 |
88 | if self.passwdCallback is not None:
89 | ctx.set_passwd_cb(self.passwdCallback)
90 |
91 | if self.keychainIdentity and hasattr(ctx, "use_keychain_identity"):
92 | ctx.use_keychain_identity(self.keychainIdentity)
93 | else:
94 | if self.certificateFileName:
95 | ctx.use_certificate_file(self.certificateFileName)
96 | if self.privateKeyFileName:
97 | ctx.use_privatekey_file(self.privateKeyFileName)
98 | if self.certificateChainFile:
99 | ctx.use_certificate_chain_file(self.certificateChainFile)
100 |
101 | verifyFlags = VERIFY_NONE
102 | if self.verifyClient:
103 | verifyFlags = VERIFY_PEER
104 | if self.requireClientCertificate:
105 | verifyFlags |= VERIFY_FAIL_IF_NO_PEER_CERT
106 | if self.verifyClientOnce:
107 | verifyFlags |= VERIFY_CLIENT_ONCE
108 | if self.clientCACertFileNames:
109 | store = ctx.get_cert_store()
110 | for cert in self.clientCACertFileNames:
111 | with open(cert) as f:
112 | certpem = f.read()
113 | cert = Certificate.loadPEM(certpem)
114 | store.add_cert(cert.original)
115 | if self.sendCAsToClient:
116 | ctx.add_client_ca(cert.original)
117 |
118 | # When a client certificate is used we also need to set a session context id
119 | # to avoid openssl SSL_F_SSL_GET_PREV_SESSION,SSL_R_SESSION_ID_CONTEXT_UNINITIALIZED
120 | # errors
121 | ctx.set_session_id(str(uuid.uuid4()).replace("-", ""))
122 |
123 | # It'd be nice if pyOpenSSL let us pass None here for this behavior (as
124 | # the underlying OpenSSL API call allows NULL to be passed). It
125 | # doesn't, so we'll supply a function which does the same thing.
126 | def _verifyCallback(conn, cert, errno, depth, preverify_ok):
127 | return preverify_ok
128 | ctx.set_verify(verifyFlags, _verifyCallback)
129 |
130 | if self.verifyClientDepth is not None:
131 | ctx.set_verify_depth(self.verifyClientDepth)
132 |
133 | if self.peerName:
134 | if hasattr(ctx, "set_peer_name"):
135 | ctx.set_peer_name(self.peerName)
136 | elif hasattr(ctx, "set_info_callback"):
137 | ctx.set_info_callback(
138 | _tolerateErrors(self._identityVerifyingInfoCallback)
139 | )
140 | else:
141 | raise ValueError("No suitable SSL API for verifying the peer host name")
142 |
143 | self._context = ctx
144 |
145 | def _identityVerifyingInfoCallback(self, connection, where, ret):
146 | """
147 | U{info_callback
148 |
149 | } for pyOpenSSL that verifies the hostname in the presented certificate
150 | matches the one passed to this L{ClientTLSOptions}.
151 |
152 | @param connection: the connection which is handshaking.
153 | @type connection: L{OpenSSL.SSL.Connection}
154 |
155 | @param where: flags indicating progress through a TLS handshake.
156 | @type where: L{int}
157 |
158 | @param ret: ignored
159 | @type ret: ignored
160 | """
161 | if where & SSL_CB_HANDSHAKE_DONE:
162 | try:
163 | hostname = self.peerName.decode("utf-8") if isinstance(self.peerName, str) else self.peerName
164 | verifyHostname(connection, hostname)
165 | except VerificationError:
166 | f = Failure()
167 | transport = connection.get_app_data()
168 | transport.failVerification(f)
169 |
170 |
171 | def simpleClientContextFactory(hostname):
172 | """
173 | Get a client context factory.
174 | """
175 | return ChainingOpenSSLContextFactory(
176 | "", "",
177 | certificateChainFile="",
178 | keychainIdentity="",
179 | peerName=hostname,
180 | )
181 |
--------------------------------------------------------------------------------
/twext/internet/tcp.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extentions to twisted.internet.tcp.
19 | """
20 |
21 | __all__ = [
22 | "MaxAcceptTCPServer",
23 | "MaxAcceptSSLServer",
24 | ]
25 |
26 | import socket
27 | from OpenSSL import SSL
28 |
29 | from twisted.application import internet
30 | from twisted.internet import tcp, ssl
31 | from twisted.internet.defer import succeed
32 |
33 | from twext.python.log import Logger
34 |
35 | log = Logger()
36 |
37 |
38 | class MaxAcceptPortMixin(object):
39 | """
40 | Mixin for resetting maxAccepts.
41 | """
42 |
43 | def doRead(self):
44 | self.numberAccepts = min(
45 | self.factory.maxRequests - self.factory.outstandingRequests,
46 | self.factory.maxAccepts
47 | )
48 | tcp.Port.doRead(self)
49 |
50 |
51 | class MaxAcceptTCPPort(MaxAcceptPortMixin, tcp.Port):
52 | """
53 | Use for non-inheriting tcp ports.
54 | """
55 |
56 |
57 | class MaxAcceptSSLPort(MaxAcceptPortMixin, ssl.Port):
58 | """
59 | Use for non-inheriting SSL ports.
60 | """
61 |
62 |
63 | class InheritedTCPPort(MaxAcceptTCPPort):
64 | """
65 | A tcp port which uses an inherited file descriptor.
66 | """
67 |
68 | def __init__(self, fd, factory, reactor):
69 | tcp.Port.__init__(self, 0, factory, reactor=reactor)
70 | # MOR: careful because fromfd dup()'s the socket, so we need to
71 | # make sure we don't leak file descriptors
72 | self.socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
73 | self._realPortNumber = self.port = self.socket.getsockname()[1]
74 |
75 | def createInternetSocket(self):
76 | return self.socket
77 |
78 | def startListening(self):
79 | log.info(
80 | "{self.factory.__class__} starting on {self._realPortNumber}",
81 | self=self
82 | )
83 | self.factory.doStart()
84 | self.connected = 1
85 | self.fileno = self.socket.fileno
86 | self.numberAccepts = self.factory.maxRequests
87 | self.startReading()
88 |
89 |
90 | class InheritedSSLPort(InheritedTCPPort):
91 | """
92 | An SSL port which uses an inherited file descriptor.
93 | """
94 |
95 | _socketShutdownMethod = 'sock_shutdown'
96 |
97 | transport = ssl.Server
98 |
99 | def __init__(self, fd, factory, ctxFactory, reactor):
100 | InheritedTCPPort.__init__(self, fd, factory, reactor)
101 | self.ctxFactory = ctxFactory
102 | self.socket = SSL.Connection(self.ctxFactory.getContext(), self.socket)
103 |
104 | def _preMakeConnection(self, transport):
105 | transport._startTLS()
106 | return tcp.Port._preMakeConnection(self, transport)
107 |
108 |
109 | def _allConnectionsClosed(protocolFactory):
110 | """
111 | Check to see if protocolFactory implements allConnectionsClosed( ) and
112 | if so, call it. Otherwise, return immediately.
113 | This allows graceful shutdown by waiting for all requests to be completed.
114 |
115 | @param protocolFactory: (usually) an HTTPFactory implementing
116 | allConnectionsClosed which returns a Deferred which fires when all
117 | connections are closed.
118 |
119 | @return: A Deferred firing None when all connections are closed, or
120 | immediately if the given factory does not track its connections (e.g.
121 | InheritingProtocolFactory)
122 | """
123 | if hasattr(protocolFactory, "allConnectionsClosed"):
124 | return protocolFactory.allConnectionsClosed()
125 | return succeed(None)
126 |
127 |
128 | class MaxAcceptTCPServer(internet.TCPServer):
129 | """
130 | TCP server which will uses MaxAcceptTCPPorts (and optionally,
131 | inherited ports)
132 |
133 | @ivar myPort: When running, this is set to the L{IListeningPort} being
134 | managed by this service.
135 | """
136 |
137 | def __init__(self, *args, **kwargs):
138 | internet.TCPServer.__init__(self, *args, **kwargs)
139 | self.protocolFactory = self.args[1]
140 | self.protocolFactory.myServer = self
141 | self.inherit = self.kwargs.get("inherit", False)
142 | self.backlog = self.kwargs.get("backlog", None)
143 | self.interface = self.kwargs.get("interface", None)
144 |
145 | def _getPort(self):
146 | from twisted.internet import reactor
147 |
148 | if self.inherit:
149 | port = InheritedTCPPort(self.args[0], self.args[1], reactor)
150 | else:
151 | port = MaxAcceptTCPPort(
152 | self.args[0], self.args[1],
153 | self.backlog, self.interface, reactor
154 | )
155 |
156 | port.startListening()
157 | self.myPort = port
158 | return port
159 |
160 | def stopService(self):
161 | """
162 | Wait for outstanding requests to finish
163 |
164 | @return: a Deferred which fires when all outstanding requests are
165 | complete
166 | """
167 | internet.TCPServer.stopService(self)
168 | return _allConnectionsClosed(self.protocolFactory)
169 |
170 |
171 | class MaxAcceptSSLServer(internet.SSLServer):
172 | """
173 | SSL server which will uses MaxAcceptSSLPorts (and optionally,
174 | inherited ports)
175 | """
176 |
177 | def __init__(self, *args, **kwargs):
178 | internet.SSLServer.__init__(self, *args, **kwargs)
179 | self.protocolFactory = self.args[1]
180 | self.protocolFactory.myServer = self
181 | self.inherit = self.kwargs.get("inherit", False)
182 | self.backlog = self.kwargs.get("backlog", None)
183 | self.interface = self.kwargs.get("interface", None)
184 |
185 | def _getPort(self):
186 | from twisted.internet import reactor
187 |
188 | if self.inherit:
189 | port = InheritedSSLPort(
190 | self.args[0], self.args[1], self.args[2], reactor
191 | )
192 | else:
193 | port = MaxAcceptSSLPort(
194 | self.args[0], self.args[1], self.args[2],
195 | self.backlog, self.interface, self.reactor
196 | )
197 |
198 | port.startListening()
199 | self.myPort = port
200 | return port
201 |
202 | def stopService(self):
203 | """
204 | Wait for outstanding requests to finish
205 |
206 | @return: a Deferred which fires when all outstanding requests are
207 | complete.
208 | """
209 | internet.SSLServer.stopService(self)
210 | # TODO: check for an ICompletionWaiter interface
211 | return _allConnectionsClosed(self.protocolFactory)
212 |
--------------------------------------------------------------------------------
/twext/internet/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.internet}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/internet/test/test_fswatch.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.internet.fswatch}.
19 | """
20 |
21 | from twext.internet.fswatch import (
22 | DirectoryChangeListener, patchReactor, IDirectoryChangeListenee
23 | )
24 | from twisted.internet.kqreactor import KQueueReactor
25 | from twisted.python.filepath import FilePath
26 | from twisted.trial.unittest import TestCase
27 | from zope.interface import implements
28 |
29 |
30 | class KQueueReactorTestFixture(object):
31 |
32 | def __init__(self, testCase, action=None, timeout=10):
33 | """
34 | Creates a kqueue reactor for use in unit tests. The reactor is patched
35 | with the vnode event handler. Once the reactor is running, it will
36 | call a supplied method. It's expected that the method will ultimately
37 | trigger the stop() of the reactor. The reactor will time out after 10
38 | seconds.
39 |
40 | @param testCase: a test method which is needed for adding cleanup to
41 | @param action: a method which will get called after the reactor is
42 | running
43 | @param timeout: how many seconds to keep the reactor running before
44 | giving up and stopping it
45 | """
46 | self.testCase = testCase
47 | self.reactor = KQueueReactor()
48 | patchReactor(self.reactor)
49 | self.action = action
50 | self.timeout = timeout
51 |
52 | def maybeStop():
53 | if self.reactor.running:
54 | return self.reactor.stop()
55 |
56 | self.testCase.addCleanup(maybeStop)
57 |
58 | def runReactor(self):
59 | """
60 | Run the test reactor, adding cleanup code to stop if after a timeout,
61 | and calling the action method
62 | """
63 | def getReadyToStop():
64 | self.reactor.callLater(self.timeout, self.reactor.stop)
65 | self.reactor.callWhenRunning(getReadyToStop)
66 | if self.action is not None:
67 | self.reactor.callWhenRunning(self.action)
68 | self.reactor.run(installSignalHandlers=False)
69 |
70 |
71 | class DataStoreMonitor(object):
72 | """
73 | Stub IDirectoryChangeListenee
74 | """
75 | implements(IDirectoryChangeListenee)
76 |
77 | def __init__(self, reactor, storageService):
78 | """
79 | @param storageService: the service making use of the DataStore
80 | directory; we send it a hardStop() to shut it down
81 | """
82 | self._reactor = reactor
83 | self._storageService = storageService
84 | self.methodCalled = ""
85 |
86 | def disconnected(self):
87 | self.methodCalled = "disconnected"
88 | self._storageService.hardStop()
89 | self._reactor.stop()
90 |
91 | def deleted(self):
92 | self.methodCalled = "deleted"
93 | self._storageService.hardStop()
94 | self._reactor.stop()
95 |
96 | def renamed(self):
97 | self.methodCalled = "renamed"
98 | self._storageService.hardStop()
99 | self._reactor.stop()
100 |
101 | def connectionLost(self, reason):
102 | pass
103 |
104 |
105 | class StubStorageService(object):
106 | """
107 | Implements hardStop for testing
108 | """
109 |
110 | def __init__(self, ignored):
111 | self.stopCalled = False
112 |
113 | def hardStop(self):
114 | self.stopCalled = True
115 |
116 |
117 | class DirectoryChangeListenerTestCase(TestCase):
118 |
119 | def test_delete(self):
120 | """
121 | Verify directory deletions can be monitored
122 | """
123 |
124 | self.tmpdir = FilePath(self.mktemp())
125 | self.tmpdir.makedirs()
126 |
127 | def deleteAction():
128 | self.tmpdir.remove()
129 |
130 | resource = KQueueReactorTestFixture(self, deleteAction)
131 | storageService = StubStorageService(resource.reactor)
132 | delegate = DataStoreMonitor(resource.reactor, storageService)
133 | dcl = DirectoryChangeListener(resource.reactor, self.tmpdir.path, delegate)
134 | dcl.startListening()
135 | resource.runReactor()
136 | self.assertTrue(storageService.stopCalled)
137 | self.assertEquals(delegate.methodCalled, "deleted")
138 |
139 | def test_rename(self):
140 | """
141 | Verify directory renames can be monitored
142 | """
143 |
144 | self.tmpdir = FilePath(self.mktemp())
145 | self.tmpdir.makedirs()
146 |
147 | def renameAction():
148 | self.tmpdir.moveTo(FilePath(self.mktemp()))
149 |
150 | resource = KQueueReactorTestFixture(self, renameAction)
151 | storageService = StubStorageService(resource.reactor)
152 | delegate = DataStoreMonitor(resource.reactor, storageService)
153 | dcl = DirectoryChangeListener(resource.reactor, self.tmpdir.path, delegate)
154 | dcl.startListening()
155 | resource.runReactor()
156 | self.assertTrue(storageService.stopCalled)
157 | self.assertEquals(delegate.methodCalled, "renamed")
158 |
--------------------------------------------------------------------------------
/twext/internet/test/test_gaiendpoint.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Test cases for L{twext.internet.gaiendpoint}
19 | """
20 |
21 | from socket import getaddrinfo, AF_INET, SOCK_STREAM
22 |
23 | from twext.internet.gaiendpoint import GAIEndpoint
24 | from twisted.trial.unittest import TestCase
25 | from twisted.internet.defer import Deferred
26 | from twisted.internet.protocol import Factory, Protocol
27 | from twisted.internet.task import Clock
28 |
29 |
30 | class FakeTCPEndpoint(object):
31 |
32 | def __init__(self, reactor, host, port, contextFactory):
33 | self._reactor = reactor
34 | self._host = host
35 | self._port = port
36 | self._attempt = None
37 | self._contextFactory = contextFactory
38 |
39 | def connect(self, factory):
40 | self._attempt = Deferred()
41 | self._factory = factory
42 | return self._attempt
43 |
44 |
45 | class GAIEndpointTestCase(TestCase):
46 | """
47 | Test cases for L{GAIEndpoint}.
48 | """
49 |
50 | def makeEndpoint(self, host="abcd.example.com", port=4321):
51 | gaie = GAIEndpoint(self.clock, host, port)
52 | gaie.subEndpoint = self.subEndpoint
53 | gaie.deferToThread = self.deferToSomething
54 | return gaie
55 |
56 | def subEndpoint(self, reactor, host, port, contextFactory):
57 | ftcpe = FakeTCPEndpoint(reactor, host, port, contextFactory)
58 | self.fakeRealEndpoints.append(ftcpe)
59 | return ftcpe
60 |
61 | def deferToSomething(self, func, *a, **k):
62 | """
63 | Test replacement for L{deferToThread}, which can only call
64 | L{getaddrinfo}.
65 | """
66 | d = Deferred()
67 | if func is not getaddrinfo:
68 | self.fail("Only getaddrinfo should be invoked in a thread.")
69 | self.inThreads.append((d, func, a, k))
70 | return d
71 |
72 | def gaiResult(self, family, socktype, proto, canonname, sockaddr):
73 | """
74 | A call to L{getaddrinfo} has succeeded; invoke the L{Deferred} waiting
75 | on it.
76 | """
77 | d, _ignore_f, _ignore_a, _ignore_k = self.inThreads.pop(0)
78 | d.callback([(family, socktype, proto, canonname, sockaddr)])
79 |
80 | def setUp(self):
81 | """
82 | Set up!
83 | """
84 | self.inThreads = []
85 | self.clock = Clock()
86 | self.fakeRealEndpoints = []
87 | self.makeEndpoint()
88 |
89 | def test_simpleSuccess(self):
90 | """
91 | If C{getaddrinfo} gives one L{GAIEndpoint.connect}.
92 | """
93 | gaiendpoint = self.makeEndpoint()
94 | protos = []
95 | f = Factory()
96 | f.protocol = Protocol
97 | gaiendpoint.connect(f).addCallback(protos.append)
98 | WHO_CARES = 0
99 | WHAT_EVER = ""
100 | self.gaiResult(AF_INET, SOCK_STREAM, WHO_CARES, WHAT_EVER,
101 | ("1.2.3.4", 4321))
102 | self.clock.advance(1.0)
103 | attempt = self.fakeRealEndpoints[0]._attempt
104 | attempt.callback(self.fakeRealEndpoints[0]._factory.buildProtocol(None))
105 | self.assertEqual(len(protos), 1)
106 |
--------------------------------------------------------------------------------
/twext/internet/threadutils.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | import sys
18 | from Queue import Queue
19 |
20 |
21 | from twisted.python.failure import Failure
22 | from twisted.internet.defer import Deferred
23 |
24 |
25 | _DONE = object()
26 |
27 | _STATE_STARTING = 'STARTING'
28 | _STATE_RUNNING = 'RUNNING'
29 | _STATE_STOPPING = 'STOPPING'
30 | _STATE_STOPPED = 'STOPPED'
31 |
32 |
33 | class ThreadHolder(object):
34 | """
35 | A queue which will hold a reactor threadpool thread open until all of the
36 | work in that queue is done.
37 | """
38 |
39 | def __init__(self, reactor):
40 | self._reactor = reactor
41 | self._state = _STATE_STOPPED
42 | self._stopper = None
43 | self._q = None
44 | self._retryCallback = None
45 |
46 | def _run(self):
47 | """
48 | Worker function which runs in a non-reactor thread.
49 | """
50 | self._state = _STATE_RUNNING
51 | while self._qpull():
52 | pass
53 |
54 | def _qpull(self):
55 | """
56 | Pull one item off the queue and react appropriately.
57 |
58 | Return whether or not to keep going.
59 | """
60 | work = self._q.get()
61 | if work is _DONE:
62 | def finishStopping():
63 | self._state = _STATE_STOPPED
64 | self._q = None
65 | s = self._stopper
66 | self._stopper = None
67 | s.callback(None)
68 | self._reactor.callFromThread(finishStopping)
69 | return False
70 | self._oneWorkUnit(*work)
71 | return True
72 |
73 | def _oneWorkUnit(self, deferred, instruction):
74 | try:
75 | result = instruction()
76 | except:
77 | etype, evalue, etb = sys.exc_info()
78 |
79 | def relayFailure():
80 | f = Failure(evalue, etype, etb)
81 | deferred.errback(f)
82 | self._reactor.callFromThread(relayFailure)
83 | else:
84 | self._reactor.callFromThread(deferred.callback, result)
85 |
86 | def submit(self, work):
87 | """
88 | Submit some work to be run.
89 |
90 | @param work: a 0-argument callable, which will be run in a thread.
91 |
92 | @return: L{Deferred} that fires with the result of L{work}
93 | """
94 | if self._state not in (_STATE_RUNNING, _STATE_STARTING):
95 | raise RuntimeError("not running")
96 | d = Deferred()
97 | self._q.put((d, work))
98 | return d
99 |
100 | def start(self):
101 | """
102 | Start this thing, if it's stopped.
103 | """
104 | if self._state != _STATE_STOPPED:
105 | raise RuntimeError("Not stopped.")
106 | self._state = _STATE_STARTING
107 | self._q = Queue(0)
108 | self._reactor.callInThread(self._run)
109 | self.retry()
110 |
111 | def retry(self):
112 | if self._state == _STATE_STARTING:
113 | if self._retryCallback is not None:
114 | self._reactor.threadpool.adjustPoolsize()
115 | self._retryCallback = self._reactor.callLater(0.1, self.retry)
116 | else:
117 | self._retryCallback = None
118 |
119 | def stop(self):
120 | """
121 | Stop this thing and release its thread, if it's running.
122 | """
123 | if self._state not in (_STATE_RUNNING, _STATE_STARTING):
124 | raise RuntimeError("Not running.")
125 | s = self._stopper = Deferred()
126 | self._state = _STATE_STOPPING
127 | self._q.put(_DONE)
128 | if self._retryCallback:
129 | self._retryCallback.cancel()
130 | self._retryCallback = None
131 | return s
132 |
--------------------------------------------------------------------------------
/twext/protocols/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2009-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extentions to L{twisted.protocols}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/protocols/echo.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Echo protocol.
19 | """
20 |
21 | __all__ = ["EchoProtocol"]
22 |
23 | from twisted.internet.protocol import Protocol
24 |
25 |
26 | class EchoProtocol(Protocol):
27 | """
28 | Say what you hear.
29 | """
30 |
31 | def dataReceived(self, data):
32 | """
33 | As soon as any data is received, write it back.
34 | """
35 | self.transport.write(data)
36 |
--------------------------------------------------------------------------------
/twext/protocols/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2009-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.protocols}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/python/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Extensions to L{twisted.python}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/python/clsprop.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2011-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | A small utility for defining static class properties.
19 | """
20 |
21 | __all__ = ["classproperty"]
22 |
23 |
24 | class classproperty(object):
25 | """
26 | Decorator for a method that wants to return a static class property. The
27 | decorated method will only be invoked once, for each class, and that value
28 | will be returned for that class.
29 | """
30 |
31 | def __init__(self, thunk=None, cache=True):
32 | self.cache = cache
33 | self.thunk = thunk
34 | self._classcache = {}
35 |
36 | def __call__(self, thunk):
37 | return self.__class__(thunk, self.cache)
38 |
39 | def __get__(self, instance, owner):
40 | if not self.cache:
41 | return self.thunk(owner)
42 |
43 | cc = self._classcache
44 |
45 | if owner in cc:
46 | cached = cc[owner]
47 | else:
48 | cached = self.thunk(owner)
49 | cc[owner] = cached
50 |
51 | return cached
52 |
--------------------------------------------------------------------------------
/twext/python/filepath.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.python.test.test_filepath -*-
2 | ##
3 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | from __future__ import absolute_import
19 |
20 | """
21 | Extend L{twisted.python.filepath} to provide performance enhancements for
22 | calendar server.
23 | """
24 |
25 | __all__ = ["CachingFilePath"]
26 |
27 | from os import listdir as listdir
28 | from os.path import join, basename, exists, dirname
29 | from time import sleep
30 | from types import FunctionType, MethodType
31 | from errno import EINVAL
32 | from stat import S_ISDIR
33 |
34 | from twisted.python.filepath import FilePath as _FilePath
35 |
36 |
37 | class CachingFilePath(_FilePath, object):
38 | """
39 | A descendent of L{_FilePath} which implements a more aggressive caching
40 | policy.
41 | """
42 |
43 | _listdir = listdir # integration points for tests
44 | _sleep = sleep
45 |
46 | BACKOFF_MAX = 5.0 # Maximum time to wait between calls to listdir()
47 |
48 | def __init__(self, path, alwaysCreate=False):
49 | super(CachingFilePath, self).__init__(path, alwaysCreate)
50 | self.existsCached = None
51 | self.isDirCached = None
52 |
53 | @property
54 | def siblingExtensionSearch(self):
55 | """
56 | Dynamically create a version of L{_FilePath.siblingExtensionSearch}
57 | that uses a pluggable L{listdir} implementation.
58 | """
59 | return MethodType(
60 | FunctionType(
61 | _FilePath.siblingExtensionSearch.im_func.func_code,
62 | {
63 | "listdir": self._retryListdir,
64 | "basename": basename,
65 | "dirname": dirname,
66 | "joinpath": join,
67 | "exists": exists
68 | }
69 | ),
70 | self, self.__class__
71 | )
72 |
73 | def changed(self):
74 | """
75 | This path may have changed in the filesystem, so forget all cached
76 | information about it.
77 | """
78 | self.statinfo = None
79 | self.existsCached = None
80 | self.isDirCached = None
81 |
82 | def _retryListdir(self, pathname):
83 | """
84 | Implementation of retry logic for C{listdir} and
85 | C{siblingExtensionSearch}.
86 | """
87 | delay = 0.1
88 |
89 | while True:
90 | try:
91 | return self._listdir(pathname)
92 | except OSError, e:
93 | if e.errno == EINVAL:
94 | self._sleep(delay)
95 | delay = min(self.BACKOFF_MAX, delay * 2.0)
96 | else:
97 | raise
98 |
99 | raise AssertionError("unreachable code.")
100 |
101 | def listdir(self):
102 | """
103 | List the directory which C{self.path} points to, compensating for
104 | EINVAL from C{os.listdir}.
105 | """
106 | return self._retryListdir(self.path)
107 |
108 | def restat(self, reraise=True):
109 | """
110 | Re-cache stat information.
111 | """
112 | try:
113 | return super(CachingFilePath, self).restat(reraise)
114 | finally:
115 | if self.statinfo:
116 | self.existsCached = True
117 | self.isDirCached = S_ISDIR(self.statinfo.st_mode)
118 | else:
119 | self.existsCached = False
120 | self.isDirCached = None
121 |
122 | def moveTo(self, destination, followLinks=True):
123 | """
124 | Override L{_FilePath.moveTo}, updating extended cache information if
125 | necessary.
126 | """
127 | result = super(CachingFilePath, self).moveTo(destination, followLinks)
128 | self.changed()
129 |
130 | # Work with vanilla FilePath destinations to pacify the tests.
131 | if hasattr(destination, "changed"):
132 | destination.changed()
133 |
134 | return result
135 |
136 | def remove(self):
137 | """
138 | Override L{_FilePath.remove}, updating extended cache information if
139 | necessary.
140 | """
141 | try:
142 | return super(CachingFilePath, self).remove()
143 | finally:
144 | self.changed()
145 |
146 |
147 | CachingFilePath.clonePath = CachingFilePath
148 |
--------------------------------------------------------------------------------
/twext/python/launchd.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.python.test.test_launchd -*-
2 | ##
3 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Binding for launchd socket hand-off API.
20 | """
21 |
22 | from __future__ import print_function
23 |
24 | from cffi import FFI, VerificationError
25 |
26 | ffi = FFI()
27 |
28 | ffi.cdef(
29 | """
30 | int launch_activate_socket(const char *name, int **fds, size_t *cnt);
31 | """
32 | )
33 |
34 | try:
35 | lib = ffi.verify(
36 | """
37 | #include
38 | """,
39 | tag=__name__.replace(".", "_")
40 | )
41 | except VerificationError as ve:
42 | raise ImportError(ve)
43 |
44 |
45 | def launchActivateSocket(name):
46 | fdList = []
47 |
48 | fds = ffi.new('int **')
49 | count = ffi.new('size_t *')
50 | result = lib.launch_activate_socket(name, fds, count)
51 | if result == 0:
52 | for i in xrange(count[0]):
53 | fdList.append(fds[0][i])
54 | return fdList
55 |
56 |
57 | __all__ = [
58 | 'launchActivateSocket',
59 | ]
60 |
--------------------------------------------------------------------------------
/twext/python/log.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.python.test.test_log-*-
2 | ##
3 | # Copyright (c) 2006-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | from twisted.logger import Logger as _Logger, LogLevel, LogPublisher, \
19 | FileLogObserver, FilteringLogObserver, LogLevelFilterPredicate, \
20 | formatEventAsClassicLogText, formatTime
21 | from twisted.python import log
22 | from twisted import logger
23 |
24 |
25 | class Logger(_Logger):
26 | """
27 | Logging object.
28 | """
29 |
30 | filterPublisher = None
31 | filterObserver = None
32 | filterPredicate = LogLevelFilterPredicate(defaultLogLevel=LogLevel.info)
33 |
34 | logBeginner = None
35 |
36 | @classmethod
37 | def makeFilteredFileLogObserver(cls, stream, withTime=True):
38 | """
39 | For a child process that has its stdout captured by the master process to be logged by the master,
40 | we strip out the time from the log entry since the master process will always add one. Setting
41 | C{withTime} to L{False} will ensure no time is generated.
42 | """
43 | assert (
44 | cls.filterPublisher is None and
45 | cls.filterObserver is None
46 | ), "Only call this once"
47 |
48 | timeFormat = formatTime if withTime else lambda _: u""
49 | cls.filterObserver = FileLogObserver(stream, lambda event: formatEventAsClassicLogText(event, formatTime=timeFormat))
50 | cls.filterPublisher = LogPublisher(cls.filterObserver)
51 | return cls.filterPublisher
52 |
53 | @classmethod
54 | def addFilteredObserver(cls, observer):
55 | log.addObserver(FilteringLogObserver(
56 | observer,
57 | [cls.filterPredicate]
58 | ))
59 |
60 | @classmethod
61 | def beginLoggingTo(cls, observers, redirectStandardIO=True):
62 | if cls.logBeginner:
63 | cls.logBeginner.beginLoggingTo(observers, redirectStandardIO=redirectStandardIO)
64 |
65 | def emit(self, level, format=None, **kwargs):
66 | """
67 | Fix {Logger.emit} to work with our legacy use and handle utf-8 properly.
68 | """
69 |
70 | # For old style logging kwargs will be empty but the format string could contain "{}", so
71 | # We turn the format string into a kwarg
72 | if len(kwargs) == 0:
73 | kwargs["msg"] = format
74 | format = "{msg}"
75 |
76 | # Assume that any L{bytes} in the format or kwargs are utf-8 encoded strings
77 | if format is not None and isinstance(format, bytes):
78 | format = format.decode("utf-8")
79 | for k, v in kwargs.items():
80 | if isinstance(v, bytes):
81 | kwargs[k] = v.decode("utf-8")
82 |
83 | super(Logger, self).emit(level, format=format, **kwargs)
84 |
85 | def levels(self):
86 | """
87 | Get the L{LogLevelFilterPredicate} associated with this logger.
88 | """
89 | return self.filterPredicate
90 |
91 |
92 | # Always replace Twisted's legacy log beginner with one that does LogLevel filtering
93 | class FilteringLogBeginnerWrapper(object):
94 |
95 | def __init__(self, beginner):
96 | self.beginner = beginner
97 |
98 | def beginLoggingTo(
99 | self, observers, discardBuffer=False, redirectStandardIO=True
100 | ):
101 | new_observers = []
102 | for observer in observers:
103 | new_observers.append(FilteringLogObserver(observer, [Logger.filterPredicate]))
104 | self.beginner.beginLoggingTo(new_observers, discardBuffer, redirectStandardIO)
105 |
106 |
107 | Logger.logBeginner = FilteringLogBeginnerWrapper(log.theLogPublisher._logBeginner)
108 | logger.globalLogBeginner = Logger.logBeginner
109 |
--------------------------------------------------------------------------------
/twext/python/parallel.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.python.test.test_parallel -*-
2 | ##
3 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Utilities for parallelizing tasks.
20 | """
21 |
22 | from twisted.internet.defer import inlineCallbacks, DeferredList, returnValue
23 |
24 |
25 | class Parallelizer(object):
26 | """
27 | Do some operation with a degree of parallelism, using a set of resources
28 | which may each only be used for one task at a time, given some underlying
29 | API that returns L{Deferreds}.
30 |
31 | @ivar available: A list of available resources from the C{resources}
32 | constructor parameter.
33 |
34 | @ivar busy: A list of resources which are currently being used by
35 | operations.
36 | """
37 |
38 | def __init__(self, resources):
39 | """
40 | Initialize a L{Parallelizer} with a list of objects that will be passed
41 | to the callables sent to L{Parallelizer.do}.
42 |
43 | @param resources: objects which may be of any arbitrary type.
44 | @type resources: C{list}
45 | """
46 | self.available = list(resources)
47 | self.busy = []
48 | self.activeDeferreds = []
49 |
50 | @inlineCallbacks
51 | def do(self, operation):
52 | """
53 | Call C{operation} with one of the resources in C{self.available},
54 | removing that value for use by other callers of C{do} until the task
55 | performed by C{operation} is complete (in other words, the L{Deferred}
56 | returned by C{operation} has fired).
57 |
58 | @param operation: a 1-argument callable taking a resource from
59 | C{self.active} and returning a L{Deferred} when it's done using
60 | that resource.
61 | @type operation: C{callable}
62 |
63 | @return: a L{Deferred} that fires as soon as there are resources
64 | available such that this task can be I{started} - not completed.
65 | """
66 | if not self.available:
67 | yield DeferredList(self.activeDeferreds, fireOnOneCallback=True,
68 | fireOnOneErrback=True)
69 | active = self.available.pop(0)
70 | self.busy.append(active)
71 | o = operation(active)
72 |
73 | def andFinally(whatever):
74 | self.activeDeferreds.remove(o)
75 | self.busy.remove(active)
76 | self.available.append(active)
77 | return whatever
78 | self.activeDeferreds.append(o)
79 | o.addBoth(andFinally)
80 | returnValue(None)
81 |
82 | def done(self):
83 | """
84 | Wait until all operations started by L{Parallelizer.do} are completed.
85 |
86 | @return: a L{Deferred} that fires (with C{None}) when all the currently
87 | pending work on this L{Parallelizer} is completed and C{busy} is
88 | empty again.
89 | """
90 | return (DeferredList(self.activeDeferreds)
91 | .addCallback(lambda ignored: None))
92 |
--------------------------------------------------------------------------------
/twext/python/sacl.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | from __future__ import print_function
18 |
19 | __all__ = [
20 | "checkSACL"
21 | ]
22 |
23 | from cffi import FFI, VerificationError
24 |
25 | ffi = FFI()
26 |
27 | definitions = """
28 | typedef unsigned char uuid_t[16];
29 | int mbr_check_service_membership(const uuid_t user, const char* servicename, int* ismember);
30 | int mbr_user_name_to_uuid(const char* name, uuid_t uu);
31 | int mbr_group_name_to_uuid(const char* name, uuid_t uu);
32 | """
33 |
34 | ffi.cdef(definitions)
35 |
36 | try:
37 | lib = ffi.verify(
38 | definitions,
39 | libraries=[],
40 | tag=__name__.replace(".", "_")
41 | )
42 | except VerificationError as ve:
43 | raise ImportError(ve)
44 |
45 |
46 | def checkSACL(userOrGroupName, serviceName):
47 | """
48 | Check to see if a given user or group is a member of an OS X Server
49 | service's access group. If userOrGroupName is an empty string, we
50 | want to know if unauthenticated access is allowed for the given service.
51 |
52 | @param userOrGroupName: the name of the user or group
53 | @type userOrGroupName: C{unicode}
54 |
55 | @param serviceName: the name of the service (e.g. calendar, addressbook)
56 | @type serviceName: C{str}
57 |
58 | @return: True if the user or group is allowed access to service
59 | @rtype: C{bool}
60 | """
61 |
62 | userOrGroupName = userOrGroupName.encode("utf-8")
63 | prefix = "com.apple.access_"
64 | uu = ffi.new("uuid_t")
65 |
66 | # See if the access group exists. If it does not, then there are no
67 | # restrictions
68 | groupName = prefix + serviceName
69 | groupMissing = lib.mbr_group_name_to_uuid(groupName, uu)
70 | if groupMissing:
71 | return True
72 |
73 | # See if userOrGroupName matches a user
74 | result = lib.mbr_user_name_to_uuid(userOrGroupName, uu)
75 | if result:
76 | # Not a user, try looking up a group of that name
77 | result = lib.mbr_group_name_to_uuid(userOrGroupName, uu)
78 |
79 | if result:
80 | # Neither a user nor a group matches the name
81 | return False
82 |
83 | # See if the uuid is a member of the service access group
84 | isMember = ffi.new("int *")
85 | result = lib.mbr_check_service_membership(uu, serviceName, isMember)
86 | if not result and isMember[0]:
87 | return True
88 |
89 | return False
90 |
--------------------------------------------------------------------------------
/twext/python/sendfd.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.python.test.test_sendmsg -*-
2 | ##
3 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | from struct import pack, unpack, calcsize
19 | from socket import SOL_SOCKET
20 |
21 | from twisted.python.sendmsg import send1msg, recv1msg, SCM_RIGHTS
22 |
23 |
24 | def sendfd(socketfd, fd, description):
25 | """
26 | Send the given FD to another process via L{send1msg} on the given
27 | C{AF_UNIX} socket.
28 |
29 | @param socketfd: An C{AF_UNIX} socket, attached to another process waiting
30 | to receive sockets via the ancillary data mechanism in L{send1msg}.
31 |
32 | @type socketfd: C{int}
33 |
34 | @param fd: A file descriptor to be sent to the other process.
35 |
36 | @type fd: C{int}
37 |
38 | @param description: a string describing the socket that was passed.
39 |
40 | @type description: C{str}
41 | """
42 | send1msg(
43 | socketfd, description, 0, [(SOL_SOCKET, SCM_RIGHTS, pack("i", fd))]
44 | )
45 |
46 |
47 | def recvfd(socketfd):
48 | """
49 | Receive a file descriptor from a L{send1msg} message on the given
50 | C{AF_UNIX} socket.
51 |
52 | @param socketfd: An C{AF_UNIX} socket, attached to another process waiting
53 | to send sockets via the ancillary data mechanism in L{send1msg}.
54 |
55 | @param fd: C{int}
56 |
57 | @return: a 2-tuple of (new file descriptor, description).
58 |
59 | @rtype: 2-tuple of (C{int}, C{str})
60 | """
61 | data, _ignore_flags, ancillary = recv1msg(socketfd)
62 | [(_ignore_cmsg_level, _ignore_cmsg_type, packedFD)] = ancillary
63 |
64 | # cmsg_level and cmsg_type really need to be SOL_SOCKET / SCM_RIGHTS, but
65 | # since those are the *only* standard values, there's not much point in
66 | # checking.
67 | unpackedFD = 0
68 | int_size = calcsize("i")
69 |
70 | if len(packedFD) > int_size: # [ar]happens on 64 bit arch (FreeBSD)
71 | [unpackedFD] = unpack("i", packedFD[0:int_size])
72 | else:
73 | [unpackedFD] = unpack("i", packedFD)
74 |
75 | return (unpackedFD, data)
76 |
--------------------------------------------------------------------------------
/twext/python/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.python}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/python/test/pullpipe.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- test-case-name: twext.python.test.test_sendmsg -*-
3 | ##
4 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | ##
18 |
19 | if __name__ == '__main__':
20 | from twext.python.sendfd import recvfd
21 | import sys
22 | import os
23 | fd, description = recvfd(int(sys.argv[1]))
24 | os.write(fd, "Test fixture data: %s.\n" % (description,))
25 | os.close(fd)
26 |
--------------------------------------------------------------------------------
/twext/python/test/test_filepath.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for specialized behavior of L{CachingFilePath}
19 | """
20 | from errno import EINVAL
21 | from os.path import join as pathjoin
22 |
23 | from twisted.internet.task import Clock
24 |
25 | from twisted.trial.unittest import TestCase
26 |
27 | from twext.python.filepath import CachingFilePath
28 |
29 | # Cheat and pull in the Twisted test cases for FilePath. XXX: Twisteds should
30 | # provide a supported way of doing this for exported interfaces. Also, it
31 | # should export IFilePath. --glyph
32 |
33 | # from twisted.test.test_paths import FilePathTests
34 |
35 | # class BaseVerification(FilePathTests):
36 | # """
37 | # Make sure that L{CachingFilePath} doesn't break the contracts that
38 | # L{FilePath} tries to provide.
39 | # """
40 |
41 | # def setUp(self):
42 | # """
43 | # Set up the test case to set the base attributes to point at
44 | # L{AbstractFilePathTestCase}.
45 | # """
46 | # FilePathTests.setUp(self)
47 | # self.root = CachingFilePath(self.root.path)
48 | # self.path = CachingFilePath(self.path.path)
49 |
50 |
51 | class EINVALTestCase(TestCase):
52 | """
53 | Sometimes, L{os.listdir} will raise C{EINVAL}. This is a transient error,
54 | and L{CachingFilePath.listdir} should work around it by retrying the
55 | C{listdir} operation until it succeeds.
56 | """
57 |
58 | def setUp(self):
59 | """
60 | Create a L{CachingFilePath} for the test to use.
61 | """
62 | self.cfp = CachingFilePath(self.mktemp())
63 | self.clock = Clock()
64 | self.cfp._sleep = self.clock.advance
65 |
66 | def test_testValidity(self):
67 | """
68 | If C{listdir} is replaced on a L{CachingFilePath}, we should be able to
69 | observe exceptions raised by the replacement. This verifies that the
70 | test patching done here is actually testing something.
71 | """
72 | class CustomException(Exception):
73 | "Just for testing."
74 | def blowUp(dirname):
75 | raise CustomException()
76 | self.cfp._listdir = blowUp
77 | self.assertRaises(CustomException, self.cfp.listdir)
78 | self.assertRaises(CustomException, self.cfp.children)
79 |
80 | def test_retryLoop(self):
81 | """
82 | L{CachingFilePath} should catch C{EINVAL} and respond by retrying the
83 | C{listdir} operation until it succeeds.
84 | """
85 | calls = []
86 |
87 | def raiseEINVAL(dirname):
88 | calls.append(dirname)
89 | if len(calls) < 5:
90 | raise OSError(EINVAL, "This should be caught by the test.")
91 | return ['a', 'b', 'c']
92 | self.cfp._listdir = raiseEINVAL
93 | self.assertEquals(self.cfp.listdir(), ['a', 'b', 'c'])
94 | self.assertEquals(self.cfp.children(), [
95 | CachingFilePath(pathjoin(self.cfp.path, 'a')),
96 | CachingFilePath(pathjoin(self.cfp.path, 'b')),
97 | CachingFilePath(pathjoin(self.cfp.path, 'c')),
98 | ])
99 |
100 | def requireTimePassed(self, filenames):
101 | """
102 | Create a replacement for listdir() which only fires after a certain
103 | amount of time.
104 | """
105 | self.calls = []
106 |
107 | def thunk(dirname):
108 | now = self.clock.seconds()
109 | if now < 20.0:
110 | self.calls.append(now)
111 | raise OSError(EINVAL, "Not enough time has passed yet.")
112 | else:
113 | return filenames
114 | self.cfp._listdir = thunk
115 |
116 | def assertRequiredTimePassed(self):
117 | """
118 | Assert that calls to the simulated time.sleep() installed by
119 | C{requireTimePassed} have been invoked the required number of times.
120 | """
121 | # Waiting should be growing by *2 each time until the additional wait
122 | # exceeds BACKOFF_MAX (5), at which point we should wait for 5s each
123 | # time.
124 | def cumulative(values):
125 | current = 0.0
126 | for value in values:
127 | current += value
128 | yield current
129 |
130 | self.assertEquals(
131 | self.calls,
132 | list(cumulative(
133 | [0.0, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 5.0, 5.0]
134 | ))
135 | )
136 |
137 | def test_backoff(self):
138 | """
139 | L{CachingFilePath} will wait for an increasing interval up to
140 | C{BACKOFF_MAX} between calls to listdir().
141 | """
142 | self.requireTimePassed(['a', 'b', 'c'])
143 | self.assertEquals(self.cfp.listdir(), ['a', 'b', 'c'])
144 |
145 | # def test_siblingExtensionSearch(self):
146 | # """
147 | # L{FilePath.siblingExtensionSearch} is unfortunately not implemented in
148 | # terms of L{FilePath.listdir}, so we need to verify that it will also
149 | # retry.
150 | # """
151 | # filenames = [self.cfp.basename() + '.a',
152 | # self.cfp.basename() + '.b',
153 | # self.cfp.basename() + '.c']
154 | # siblings = map(self.cfp.sibling, filenames)
155 | # for sibling in siblings:
156 | # sibling.touch()
157 | # self.requireTimePassed(filenames)
158 | # self.assertEquals(self.cfp.siblingExtensionSearch("*"),
159 | # siblings[0])
160 | # self.assertRequiredTimePassed()
161 |
--------------------------------------------------------------------------------
/twext/python/test/test_launchd.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.python.launchd}.
19 | """
20 |
21 | from subprocess import Popen, PIPE
22 | import json
23 | import os
24 | import plistlib
25 | import socket
26 | import sys
27 |
28 | if __name__ == '__main__':
29 | # This module is loaded as a launchd job by test-cases below; the following
30 | # code looks up an appropriate function to run.
31 | testID = sys.argv[1]
32 | a, b = testID.rsplit(".", 1)
33 | from twisted.python.reflect import namedAny
34 | try:
35 | namedAny(".".join([a, b.replace("test_", "job_")]))()
36 | finally:
37 | sys.stdout.flush()
38 | sys.stderr.flush()
39 | skt = socket.socket()
40 | skt.connect(("127.0.0.1", int(os.environ["TESTING_PORT"])))
41 | sys.exit(0)
42 |
43 |
44 | try:
45 | from twext.python.launchd import launchActivateSocket
46 | except ImportError:
47 | skip = "LaunchD not available."
48 | else:
49 | skip = False
50 |
51 | from twisted.trial.unittest import TestCase, SkipTest
52 | from twisted.python.filepath import FilePath
53 |
54 |
55 | class CheckInTests(TestCase):
56 | """
57 | Integration tests making sure that actual checkin with launchd results in
58 | the expected values.
59 | """
60 |
61 | def setUp(self):
62 | fp = FilePath(self.mktemp())
63 | fp.makedirs()
64 | from twisted.internet.protocol import Protocol, Factory
65 | from twisted.internet import reactor, defer
66 | d = defer.Deferred()
67 |
68 | class JustLetMeMoveOn(Protocol):
69 |
70 | def connectionMade(self):
71 | d.callback(None)
72 | self.transport.abortConnection()
73 | f = Factory()
74 | f.protocol = JustLetMeMoveOn
75 | port = reactor.listenTCP(0, f, interface="127.0.0.1")
76 |
77 | @self.addCleanup
78 | def goodbyePort():
79 | return port.stopListening()
80 | env = dict(os.environ)
81 | env["TESTING_PORT"] = repr(port.getHost().port)
82 | self.stdout = fp.child("stdout.txt")
83 | self.stderr = fp.child("stderr.txt")
84 | self.launchLabel = ("org.calendarserver.UNIT-TESTS." +
85 | str(os.getpid()) + "." + self.id())
86 | plist = {
87 | "Label": self.launchLabel,
88 | "ProgramArguments": [sys.executable, "-m", __name__, self.id()],
89 | "EnvironmentVariables": env,
90 | "KeepAlive": False,
91 | "StandardOutPath": self.stdout.path,
92 | "StandardErrorPath": self.stderr.path,
93 | "Sockets": {
94 | "Awesome": [{"SecureSocketWithKey": "GeneratedSocket"}]
95 | },
96 | "RunAtLoad": True,
97 | "Debug": True,
98 | }
99 | self.job = fp.child("job.plist")
100 | self.job.setContent(plistlib.writePlistToString(plist))
101 |
102 | child = Popen(
103 | args=[
104 | "launchctl",
105 | "load",
106 | self.job.path,
107 | ],
108 | stdout=PIPE, stderr=PIPE,
109 | )
110 | _ignore_output, error = child.communicate()
111 |
112 | if child.returncode != 0 or error:
113 | raise SkipTest("launchctl cannot run on this system")
114 |
115 | return d
116 |
117 | @staticmethod
118 | def job_test():
119 | """
120 | Do something observable in a subprocess.
121 | """
122 | sys.stdout.write("Sample Value.")
123 | sys.stdout.flush()
124 |
125 | def test_test(self):
126 | """
127 | Since this test framework is somewhat finicky, let's just make sure
128 | that a test can complete.
129 | """
130 | self.assertEquals("Sample Value.", self.stdout.getContent())
131 |
132 | @staticmethod
133 | def job_getFDs():
134 | """
135 | Check-in via the high-level C{getLaunchDSocketFDs} API, that just gives
136 | us listening FDs.
137 | """
138 | sys.stdout.write(json.dumps(launchActivateSocket("Awesome")))
139 |
140 | def test_getFDs(self):
141 | """
142 | L{getLaunchDSocketFDs} returns a Python dictionary mapping the names of
143 | sockets specified in the property list to lists of integers
144 | representing FDs.
145 | """
146 | sockets = json.loads(self.stdout.getContent())
147 | self.assertEquals(len(sockets), 1)
148 | self.assertIsInstance(sockets[0], int)
149 |
150 | def tearDown(self):
151 | """
152 | Un-load the launchd job and report any errors it encountered.
153 | """
154 | os.spawnlp(os.P_WAIT, "launchctl",
155 | "launchctl", "unload", self.job.path)
156 | err = self.stderr.getContent()
157 | if 'Traceback' in err:
158 | self.fail(err)
159 |
--------------------------------------------------------------------------------
/twext/python/test/test_log.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2005-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | from cStringIO import StringIO
18 | from twext.python.log import Logger
19 | from twisted.logger import LogLevel, LogPublisher, textFileLogObserver
20 | from twisted.trial import unittest
21 |
22 |
23 | class LogPublisherTests(unittest.TestCase):
24 | """
25 | Tests for L{Logger}.
26 | """
27 |
28 | def test_old_style(self):
29 | """
30 | L{Logger} handles old style log strings.
31 | """
32 | observer = LogPublisher()
33 |
34 | observed = []
35 | observer.addObserver(observed.append)
36 |
37 | sio = StringIO()
38 | observer.addObserver(textFileLogObserver(sio))
39 |
40 | logger = Logger(observer=observer)
41 |
42 | index = 0
43 | logger.info("test")
44 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
45 | self.assertEqual(observed[index]["log_format"], u"{msg}")
46 | self.assertEqual(observed[index]["msg"], u"test")
47 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "test")
48 |
49 | index += 1
50 | logger.info("test {}")
51 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
52 | self.assertEqual(observed[index]["log_format"], u"{msg}")
53 | self.assertEqual(observed[index]["msg"], u"test {}")
54 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "test {}")
55 |
56 | index += 1
57 | logger.info("test {foo}")
58 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
59 | self.assertEqual(observed[index]["log_format"], u"{msg}")
60 | self.assertEqual(observed[index]["msg"], u"test {foo}")
61 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "test {foo}")
62 |
63 | def test_utf8(self):
64 | """
65 | L{Logger} handles utf8 log strings and format args.
66 | """
67 | observer = LogPublisher()
68 |
69 | observed = []
70 | observer.addObserver(observed.append)
71 |
72 | sio = StringIO()
73 | observer.addObserver(textFileLogObserver(sio))
74 |
75 | logger = Logger(observer=observer)
76 |
77 | index = 0
78 | logger.info("t\xc3\xa9st")
79 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
80 | self.assertEqual(observed[index]["log_format"], u"{msg}")
81 | self.assertEqual(observed[index]["msg"], u"t\xe9st")
82 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "t\xc3\xa9st")
83 |
84 | index += 1
85 | logger.info("{str}", str="t\xc3\xa9st")
86 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
87 | self.assertEqual(observed[index]["log_format"], u"{str}")
88 | self.assertEqual(observed[index]["str"], u"t\xe9st")
89 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "t\xc3\xa9st")
90 |
91 | index += 1
92 | logger.info("T\xc3\xa9st {str}", str="t\xc3\xa9st")
93 | self.assertEqual(observed[index]["log_level"], LogLevel.info)
94 | self.assertEqual(observed[index]["log_format"], u"T\xe9st {str}")
95 | self.assertEqual(observed[index]["str"], u"t\xe9st")
96 | self.assertEqual(sio.getvalue().splitlines()[index].split("#info] ")[1], "T\xc3\xa9st t\xc3\xa9st")
97 |
--------------------------------------------------------------------------------
/twext/python/test/test_parallel.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2012-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.python.parallel}.
19 | """
20 |
21 | from twisted.internet.defer import Deferred
22 |
23 | from twext.python.parallel import Parallelizer
24 |
25 | from twisted.trial.unittest import TestCase
26 |
27 |
28 | class ParallelizerTests(TestCase):
29 | """
30 | Tests for L{Parallelizer}.
31 | """
32 |
33 | def test_doAndDone(self):
34 | """
35 | Blanket catch-all test. (TODO: split this up into more nice
36 | fine-grained tests.)
37 | """
38 | d1 = Deferred()
39 | d2 = Deferred()
40 | d3 = Deferred()
41 | d4 = Deferred()
42 | doing = []
43 | done = []
44 | allDone = []
45 | p = Parallelizer(['a', 'b', 'c'])
46 | p.do(lambda a: doing.append(a) or d1).addCallback(done.append)
47 | p.do(lambda b: doing.append(b) or d2).addCallback(done.append)
48 | p.do(lambda c: doing.append(c) or d3).addCallback(done.append)
49 | p.do(lambda b1: doing.append(b1) or d4).addCallback(done.append)
50 | p.done().addCallback(allDone.append)
51 | self.assertEqual(allDone, [])
52 | self.assertEqual(doing, ['a', 'b', 'c'])
53 | self.assertEqual(done, [None, None, None])
54 | d2.callback(1)
55 | self.assertEqual(doing, ['a', 'b', 'c', 'b'])
56 | self.assertEqual(done, [None, None, None, None])
57 | self.assertEqual(allDone, [])
58 | d3.callback(2)
59 | d4.callback(3)
60 | d1.callback(4)
61 | self.assertEqual(done, [None, None, None, None])
62 | self.assertEqual(allDone, [None])
63 |
--------------------------------------------------------------------------------
/twext/python/types.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2011-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Utilities related to types.
19 | """
20 |
21 | __all__ = [
22 | "MappingProxyType",
23 | ]
24 |
25 |
26 | class MappingProxyType(object):
27 | """
28 | Read-only proxy of a mapping. It provides a dynamic view on the mapping's
29 | entries, which means that when the mapping changes, the view reflects these
30 | changes.
31 |
32 | Note that for an ummutable mapping, one would have to prevent modifications
33 | to the underlying mapping. Note also that mutable values remain mutable
34 | when accessed via a proxy.
35 |
36 | Backport of Python 3's L{types.MappingProxyType
37 | }.
38 | """
39 |
40 | def __init__(self, mapping):
41 | """
42 | @param mapping: A mapping to wrap.
43 | @type mapping: mapping
44 | """
45 | self._mapping = mapping
46 |
47 | def __len__(self):
48 | return len(self._mapping)
49 |
50 | def __getitem__(self, key):
51 | return self._mapping[key]
52 |
53 | def __iter__(self):
54 | return iter(self._mapping)
55 |
56 | def __reversed__(self):
57 | return reversed(self._mapping)
58 |
59 | def __contains__(self, key):
60 | return key in self._mapping
61 |
62 | def copy(self):
63 | return self._mapping.copy()
64 |
65 | def get(self, key, default=None):
66 | return self._mapping.get(key, default)
67 |
68 | def has_key(self, key):
69 | return key in self._mapping
70 |
71 | def items(self):
72 | return self._mapping.items()
73 |
74 | def iteritems(self):
75 | return self._mapping.iteritems()
76 |
77 | def iterkeys(self):
78 | return self._mapping.iterkeys()
79 |
80 | def itervalues(self):
81 | return self._mapping.itervalues()
82 |
83 | def keys(self):
84 | return self._mapping.keys()
85 |
86 | def values(self):
87 | return self._mapping.values()
88 |
89 | def viewitems(self):
90 | return self._mapping.viewitems()
91 |
92 | def viewkeys(self):
93 | return self._mapping.viewkeys()
94 |
95 | def viewvalues(self):
96 | return self._mapping.viewvalues()
97 |
--------------------------------------------------------------------------------
/twext/who/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.who.test -*-
2 | ##
3 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Directory service integration.
20 | """
21 |
--------------------------------------------------------------------------------
/twext/who/aggregate.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.who.test.test_aggregate -*-
2 | ##
3 | # Copyright (c) 2006-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Directory service which aggregates multiple directory services.
20 | """
21 |
22 | __all__ = [
23 | "DirectoryService",
24 | ]
25 |
26 | import collections
27 | from itertools import chain
28 |
29 | from twisted.python.components import proxyForInterface
30 | from twisted.internet.defer import (
31 | gatherResults, FirstError, succeed, inlineCallbacks, returnValue
32 | )
33 |
34 | from .idirectory import DirectoryConfigurationError, IDirectoryService
35 | from .directory import (
36 | DirectoryService as BaseDirectoryService
37 | )
38 | from .util import ConstantsContainer
39 |
40 |
41 | class DirectoryService(BaseDirectoryService):
42 | """
43 | Aggregate directory service.
44 | """
45 |
46 | def __init__(self, realmName, services):
47 | recordTypes = set()
48 |
49 | for service in services:
50 | if not IDirectoryService.implementedBy(service.__class__):
51 | raise ValueError(
52 | "Not a directory service: {0}".format(service)
53 | )
54 |
55 | service = proxyForInterface(
56 | IDirectoryService, originalAttribute="_service"
57 | )(service)
58 |
59 | for recordType in service.recordTypes():
60 | if recordType in recordTypes:
61 | raise DirectoryConfigurationError(
62 | "Aggregated services may not vend "
63 | "the same record type: {0}"
64 | .format(recordType)
65 | )
66 | recordTypes.add(recordType)
67 |
68 | BaseDirectoryService.__init__(self, realmName)
69 |
70 | self._services = tuple(services)
71 |
72 | @property
73 | def services(self):
74 | return self._services
75 |
76 | @property
77 | def recordType(self):
78 | if not hasattr(self, "_recordType"):
79 | self._recordType = ConstantsContainer(tuple(
80 | s.recordTypes()
81 | for s in self.services
82 | ))
83 | return self._recordType
84 |
85 | @inlineCallbacks
86 | def _oneFromSubServices(self, methodName, *args, **kwargs):
87 | for service in self.services:
88 | m = getattr(service, methodName)
89 | record = yield m(*args, **kwargs)
90 |
91 | if record is not None:
92 | returnValue(record)
93 |
94 | returnValue(None)
95 |
96 | def _gatherFromSubServices(self, methodName, *args, **kwargs):
97 | ds = []
98 | for service in self.services:
99 | m = getattr(service, methodName)
100 | d = m(*args, **kwargs)
101 | ds.append(d)
102 |
103 | def unwrapFirstError(f):
104 | f.trap(FirstError)
105 | return f.value.subFailure
106 |
107 | d = gatherResults(ds, consumeErrors=True)
108 | d.addCallback(lambda results: chain(*results))
109 | d.addErrback(unwrapFirstError)
110 | return d
111 |
112 | def recordsFromExpression(
113 | self, expression, recordTypes=None, records=None,
114 | limitResults=None, timeoutSeconds=None
115 | ):
116 | return self._gatherFromSubServices(
117 | "recordsFromExpression", expression, recordTypes=recordTypes,
118 | records=None,
119 | limitResults=limitResults, timeoutSeconds=timeoutSeconds
120 | )
121 |
122 | # Implementation of recordWith*() methods may seem unnecessary here, since
123 | # they eventually end up at recordsFromExpression() anyway (in our
124 | # superclass).
125 | # However, the wrapped services may have optimzed versions of these, so we
126 | # want to call their implementations, not bypass them.
127 |
128 | def recordsWithFieldValue(
129 | self, fieldName, value, limitResults=None, timeoutSeconds=None
130 | ):
131 | return self._gatherFromSubServices(
132 | "recordsWithFieldValue", fieldName, value,
133 | limitResults=limitResults, timeoutSeconds=timeoutSeconds
134 | )
135 |
136 | def recordWithUID(self, uid, timeoutSeconds=None):
137 | return self._oneFromSubServices(
138 | "recordWithUID", uid, timeoutSeconds=timeoutSeconds
139 | )
140 |
141 | def recordWithGUID(self, guid, timeoutSeconds=None):
142 | return self._oneFromSubServices(
143 | "recordWithGUID", guid, timeoutSeconds=timeoutSeconds
144 | )
145 |
146 | def recordsWithRecordType(
147 | self, recordType, limitResults=None, timeoutSeconds=None
148 | ):
149 | # Since we know the recordType, we can go directly to the appropriate
150 | # service.
151 | for service in self.services:
152 | if recordType in service.recordTypes():
153 | return service.recordsWithRecordType(
154 | recordType,
155 | limitResults=limitResults, timeoutSeconds=timeoutSeconds
156 | )
157 | return succeed(())
158 |
159 | def recordWithShortName(self, recordType, shortName, timeoutSeconds=None):
160 | # Since we know the recordType, we can go directly to the appropriate
161 | # service.
162 | for service in self.services:
163 | if recordType in service.recordTypes():
164 | return service.recordWithShortName(
165 | recordType, shortName, timeoutSeconds=timeoutSeconds
166 | )
167 | return succeed(None)
168 |
169 | def recordsWithEmailAddress(
170 | self, emailAddress, limitResults=None, timeoutSeconds=None
171 | ):
172 | return self._gatherFromSubServices(
173 | "recordsWithEmailAddress", emailAddress,
174 | limitResults=limitResults, timeoutSeconds=timeoutSeconds
175 | )
176 |
177 | @inlineCallbacks
178 | def updateRecords(self, records, create=False):
179 | # When migrating there may be lots of new records so batch this by each
180 | # service record type.
181 | recordsByType = collections.defaultdict(list)
182 | for record in records:
183 | recordsByType[record.recordType].append(record)
184 |
185 | for recordType, recordList in recordsByType.items():
186 | for service in self.services:
187 | if recordType in service.recordTypes():
188 | yield service.updateRecords(recordList, create=create)
189 |
190 | @inlineCallbacks
191 | def removeRecords(self, uids):
192 | # FIXME: since we don't know which sub-service owns each uid, we
193 | # currently try removing the uids in each sub-service, ignoring
194 | # errors.
195 | for service in self.services:
196 | try:
197 | yield service.removeRecords(uids)
198 | except:
199 | pass
200 |
201 | @inlineCallbacks
202 | def flush(self):
203 | for service in self.services:
204 | try:
205 | yield service.flush()
206 | except:
207 | pass
208 |
--------------------------------------------------------------------------------
/twext/who/checker.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.who.test.test_checker -*-
2 | ##
3 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | L{twisted.cred}-style credential checker.
20 | """
21 |
22 | from zope.interface import implementer, classImplements, Attribute
23 |
24 | from twisted.internet.defer import inlineCallbacks, returnValue
25 | from twisted.cred.error import UnauthorizedLogin
26 | from twisted.cred.checkers import ICredentialsChecker
27 | from twisted.cred.credentials import (
28 | ICredentials, IUsernamePassword, IUsernameHashedPassword,
29 | DigestedCredentials,
30 | )
31 |
32 | from .idirectory import (
33 | IDirectoryService, RecordType,
34 | IPlaintextPasswordVerifier, IHTTPDigestVerifier,
35 | )
36 |
37 |
38 | @implementer(ICredentialsChecker)
39 | class BaseCredentialChecker(object):
40 | # credentialInterfaces = (IUsernamePassword, IUsernameHashedPassword)
41 |
42 | def __init__(self, service):
43 | """
44 | @param service: The directory service to use to obtain directory
45 | records and validate credentials against.
46 | @type service: L{IDirectoryService}
47 | """
48 | if not IDirectoryService.providedBy(service):
49 | raise TypeError("Not an IDirectoryService: {0!r}".format(service))
50 |
51 | self.service = service
52 |
53 |
54 | class UsernamePasswordCredentialChecker(BaseCredentialChecker):
55 | credentialInterfaces = (IUsernamePassword, IUsernameHashedPassword)
56 |
57 | @inlineCallbacks
58 | def requestAvatarId(self, credentials):
59 | if not IUsernamePassword.providedBy(credentials):
60 | raise TypeError(
61 | "Not an IUsernamePassword: {0!r}".format(credentials)
62 | )
63 |
64 | record = yield self.service.recordWithShortName(
65 | RecordType.user, credentials.username.decode("utf-8")
66 | )
67 |
68 | if record is None:
69 | raise UnauthorizedLogin("No such user")
70 |
71 | if not IPlaintextPasswordVerifier.providedBy(record):
72 | raise UnauthorizedLogin(
73 | "Not an IPlaintextPasswordVerifier: {0!r}".format(record)
74 | )
75 |
76 | auth = yield record.verifyPlaintextPassword(credentials.password)
77 | if auth:
78 | returnValue(record)
79 |
80 | raise UnauthorizedLogin("Incorrect password")
81 |
82 |
83 | class IHTTPDigest(ICredentials):
84 | """
85 | HTTP digest credentials.
86 | """
87 | username = Attribute("User (short) name")
88 | method = Attribute("...")
89 | fields = Attribute("...") # Attributes would be better.
90 |
91 |
92 | classImplements(DigestedCredentials, (IHTTPDigest,))
93 |
94 |
95 | class HTTPDigestCredentialChecker(BaseCredentialChecker):
96 | credentialInterfaces = (IHTTPDigest,)
97 |
98 | @inlineCallbacks
99 | def requestAvatarId(self, credentials):
100 | if not IHTTPDigest.providedBy(credentials):
101 | raise TypeError(
102 | "Not an IHTTPDigest: {0!r}".format(credentials)
103 | )
104 |
105 | record = yield self.service.recordWithShortName(
106 | RecordType.user, credentials.username.decode("utf-8")
107 | )
108 |
109 | if record is None:
110 | raise UnauthorizedLogin("No such user")
111 |
112 | if not IHTTPDigestVerifier.providedBy(record):
113 | raise UnauthorizedLogin(
114 | "Not an IHTTPDigestVerifier: {0!r}".format(record)
115 | )
116 |
117 | result = yield record.verifyHTTPDigest(
118 | credentials.username,
119 | credentials.fields.get("realm"),
120 | credentials.fields.get("uri"),
121 | credentials.fields.get("nonce"),
122 | credentials.fields.get("cnonce"),
123 | credentials.fields.get("algorithm", "md5"),
124 | credentials.fields.get("nc"),
125 | credentials.fields.get("qop"),
126 | credentials.fields.get("response"),
127 | credentials.method,
128 | )
129 | if result:
130 | returnValue(record)
131 |
132 | raise UnauthorizedLogin("Incorrect password")
133 |
--------------------------------------------------------------------------------
/twext/who/ldap/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2014-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | LDAP directory service.
19 | """
20 |
21 | __all__ = [
22 | "LDAPError",
23 | "LDAPConfigurationError",
24 | "LDAPConnectionError",
25 | "LDAPBindAuthError",
26 | "LDAPQueryError",
27 | "RecordTypeSchema",
28 | "DirectoryService",
29 | "LDAPAttribute",
30 | "LDAPObjectClass",
31 | "FieldName",
32 | ]
33 |
34 |
35 | from ._service import (
36 | LDAPError,
37 | LDAPConfigurationError,
38 | LDAPConnectionError,
39 | LDAPBindAuthError,
40 | LDAPQueryError,
41 | RecordTypeSchema,
42 | DirectoryService,
43 | FieldName
44 | )
45 | from ._constants import (
46 | LDAPAttribute,
47 | LDAPObjectClass,
48 | )
49 |
--------------------------------------------------------------------------------
/twext/who/ldap/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2014-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
--------------------------------------------------------------------------------
/twext/who/opendirectory/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | OpenDirectory directory service.
19 | """
20 |
21 | __all__ = [
22 | "OpenDirectoryError",
23 | "OpenDirectoryConnectionError",
24 | "OpenDirectoryQueryError",
25 | "OpenDirectoryDataError",
26 | "DirectoryService",
27 | "NoQOPDigestCredentialFactory",
28 | "RecordType",
29 | ]
30 |
31 |
32 | from ._service import (
33 | OpenDirectoryError, OpenDirectoryConnectionError, OpenDirectoryQueryError,
34 | OpenDirectoryDataError,
35 | DirectoryService,
36 | NoQOPDigestCredentialFactory,
37 | )
38 |
--------------------------------------------------------------------------------
/twext/who/opendirectory/_odframework.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | OpenDirectory.framework
19 | """
20 |
21 | import objc as _objc
22 |
23 | __bundle__ = _objc.initFrameworkWrapper(
24 | "OpenDirectory",
25 | frameworkIdentifier="com.apple.OpenDirectory",
26 | frameworkPath=_objc.pathForFramework(
27 | "/System/Library/Frameworks/OpenDirectory.framework"
28 | ),
29 | globals=globals()
30 | )
31 |
--------------------------------------------------------------------------------
/twext/who/opendirectory/_scripts.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | import sys
18 | import md5
19 | import sha
20 | from getpass import getpass
21 |
22 | from twisted.internet.defer import inlineCallbacks
23 | from twisted.cred.credentials import UsernamePassword, DigestedCredentials
24 | from twisted.cred.error import UnauthorizedLogin
25 |
26 | from twext.who.expression import (
27 | MatchExpression, MatchType, CompoundExpression, Operand,
28 | )
29 | from twext.who.opendirectory import DirectoryService
30 |
31 |
32 | algorithms = {
33 | "md5": md5.new,
34 | "md5-sess": md5.new,
35 | "sha": sha.new,
36 | }
37 |
38 |
39 | # DigestCalcHA1
40 | def calcHA1(
41 | pszAlg,
42 | pszUserName,
43 | pszRealm,
44 | pszPassword,
45 | pszNonce,
46 | pszCNonce,
47 | preHA1=None
48 | ):
49 | """
50 | @param pszAlg: The name of the algorithm to use to calculate the digest.
51 | Currently supported are md5 md5-sess and sha.
52 |
53 | @param pszUserName: The username
54 | @param pszRealm: The realm
55 | @param pszPassword: The password
56 | @param pszNonce: The nonce
57 | @param pszCNonce: The cnonce
58 |
59 | @param preHA1: If available this is a str containing a previously
60 | calculated HA1 as a hex string. If this is given then the values for
61 | pszUserName, pszRealm, and pszPassword are ignored.
62 | """
63 |
64 | if (preHA1 and (pszUserName or pszRealm or pszPassword)):
65 | raise TypeError(("preHA1 is incompatible with the pszUserName, "
66 | "pszRealm, and pszPassword arguments"))
67 |
68 | if preHA1 is None:
69 | # We need to calculate the HA1 from the username:realm:password
70 | m = algorithms[pszAlg]()
71 | m.update(pszUserName)
72 | m.update(":")
73 | m.update(pszRealm)
74 | m.update(":")
75 | m.update(pszPassword)
76 | HA1 = m.digest()
77 | else:
78 | # We were given a username:realm:password
79 | HA1 = preHA1.decode("hex")
80 |
81 | if pszAlg == "md5-sess":
82 | m = algorithms[pszAlg]()
83 | m.update(HA1)
84 | m.update(":")
85 | m.update(pszNonce)
86 | m.update(":")
87 | m.update(pszCNonce)
88 | HA1 = m.digest()
89 |
90 | return HA1.encode("hex")
91 |
92 |
93 | # DigestCalcResponse
94 | def calcResponse(
95 | HA1,
96 | algo,
97 | pszNonce,
98 | pszNonceCount,
99 | pszCNonce,
100 | pszQop,
101 | pszMethod,
102 | pszDigestUri,
103 | pszHEntity,
104 | ):
105 | m = algorithms[algo]()
106 | m.update(pszMethod)
107 | m.update(":")
108 | m.update(pszDigestUri)
109 | if pszQop == "auth-int" or pszQop == "auth-conf":
110 | m.update(":")
111 | m.update(pszHEntity)
112 | HA2 = m.digest().encode("hex")
113 |
114 | m = algorithms[algo]()
115 | m.update(HA1)
116 | m.update(":")
117 | m.update(pszNonce)
118 | m.update(":")
119 | if pszNonceCount and pszCNonce and pszQop:
120 | m.update(pszNonceCount)
121 | m.update(":")
122 | m.update(pszCNonce)
123 | m.update(":")
124 | m.update(pszQop)
125 | m.update(":")
126 | m.update(HA2)
127 | respHash = m.digest().encode("hex")
128 | return respHash
129 |
130 |
131 | @inlineCallbacks
132 | def authUsernamePassword(username, password):
133 | # Authenticate using simple password
134 |
135 | service = DirectoryService()
136 |
137 | creds = UsernamePassword(username, password)
138 | try:
139 | id = yield service.requestAvatarId(creds)
140 | print("OK via UsernamePassword, avatarID: {id}".format(id=id))
141 | print(" {name}".format(name=id.fullNames))
142 | except UnauthorizedLogin:
143 | print("Via UsernamePassword, could not authenticate")
144 |
145 | print("")
146 |
147 | # Authenticate using Digest
148 |
149 | algorithm = "md5" # "md5-sess"
150 | cnonce = "/rrD6TqPA3lHRmg+fw/vyU6oWoQgzK7h9yWrsCmv/lE="
151 | entity = "00000000000000000000000000000000"
152 | method = "GET"
153 | nc = "00000001"
154 | nonce = "128446648710842461101646794502"
155 | qop = None
156 | realm = "host.example.com"
157 | uri = "http://host.example.com"
158 |
159 | responseHash = calcResponse(
160 | calcHA1(
161 | algorithm.lower(), username, realm, password, nonce, cnonce
162 | ),
163 | algorithm.lower(), nonce, nc, cnonce, qop, method, uri, entity
164 | )
165 |
166 | response = (
167 | 'Digest username="{username}", uri="{uri}", response={hash}'.format(
168 | username=username, uri=uri, hash=responseHash
169 | )
170 | )
171 |
172 | fields = {
173 | "realm": realm,
174 | "nonce": nonce,
175 | "response": response,
176 | "algorithm": algorithm,
177 | }
178 |
179 | creds = DigestedCredentials(username, method, realm, fields)
180 |
181 | try:
182 | id = yield service.requestAvatarId(creds)
183 | print("OK via DigestedCredentials, avatarID: {id}".format(id=id))
184 | print(" {name}".format(name=id.fullNames))
185 | except UnauthorizedLogin:
186 | print("Via DigestedCredentials, could not authenticate")
187 |
188 |
189 | @inlineCallbacks
190 | def lookup(shortNames):
191 | service = DirectoryService()
192 | print(
193 | "Service = {service}\n"
194 | "Session = {service.session}\n"
195 | "Node = {service.node}\n"
196 | # "Local node = {service.localNode}\n"
197 | .format(service=service)
198 | )
199 | print("-" * 80)
200 |
201 | for shortName in shortNames:
202 | print("Looking up short name: {0}".format(shortName))
203 |
204 | record = yield service.recordWithShortName(service.recordType.user, shortName)
205 | if record:
206 | print(record.description())
207 |
208 | continue
209 |
210 | matchExpression = MatchExpression(
211 | service.fieldName.shortNames, shortName,
212 | matchType=MatchType.equals,
213 | )
214 |
215 | records = yield service.recordsFromExpression(matchExpression)
216 | for record in records:
217 | print(record.description())
218 |
219 | compoundExpression = CompoundExpression(
220 | [
221 | MatchExpression(
222 | service.fieldName.shortNames, shortName,
223 | matchType=MatchType.contains
224 | ),
225 | MatchExpression(
226 | service.fieldName.emailAddresses, shortName,
227 | matchType=MatchType.contains
228 | ),
229 | ],
230 | Operand.OR
231 | )
232 |
233 | records = yield service.recordsFromExpression(compoundExpression)
234 | for record in records:
235 | print(record.description())
236 |
237 |
238 | def run_auth():
239 | username = raw_input("Username: ")
240 | if username:
241 | password = getpass()
242 | if password:
243 | authUsernamePassword(username, password)
244 |
245 |
246 | def run_lookup():
247 | lookup(sys.argv[1:])
248 |
--------------------------------------------------------------------------------
/twext/who/opendirectory/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
--------------------------------------------------------------------------------
/twext/who/test/__init__.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Tests for L{twext.who}.
19 | """
20 |
--------------------------------------------------------------------------------
/twext/who/test/auth_resource.rpy:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | #
18 | # Run:
19 | # twistd -n web --path twext/who/test
20 | #
21 | # And open this URL:
22 | # http://localhost:8080/auth_resource.rpy
23 | #
24 |
25 | cache()
26 |
27 | from tempfile import NamedTemporaryFile
28 | from textwrap import dedent
29 |
30 | from twisted.cred.portal import Portal
31 | from twisted.web.resource import IResource
32 | from twisted.web.guard import (
33 | HTTPAuthSessionWrapper,
34 | BasicCredentialFactory,
35 | DigestCredentialFactory,
36 | )
37 | from twisted.web.static import Data
38 |
39 | from twext.who.directory import DirectoryRecord
40 |
41 | from twext.who.test.test_xml import xmlService as XMLDirectoryService
42 |
43 | try:
44 | from twext.who.ldap.test.test_service import BaseTestCase as LDAPScaffold
45 | LDAPDirectoryService = LDAPScaffold.service
46 |
47 | except ImportError:
48 | LDAPDirectoryService = None
49 |
50 | try:
51 | from twext.who.opendirectory import (
52 | DirectoryService as OpenDirectoryDirectoryService,
53 | NoQOPDigestCredentialFactory,
54 | )
55 | except ImportError:
56 | OpenDirectoryDirectoryService = None
57 |
58 | from twext.who.checker import UsernamePasswordCredentialChecker
59 | from twext.who.checker import HTTPDigestCredentialChecker
60 |
61 |
62 |
63 | class Realm(object):
64 | @staticmethod
65 | def hello(avatarId):
66 | message = ["Hello, {0!r}!".format(avatarId)]
67 |
68 | if isinstance(avatarId, DirectoryRecord):
69 | message.append(avatarId.description().encode("utf-8"))
70 |
71 | return "\n\n".join(message)
72 |
73 |
74 | def requestAvatar(self, avatarId, mind, *interfaces):
75 | if IResource in interfaces:
76 | interface = IResource
77 | resource = Data(self.hello(avatarId), "text/plain")
78 | else:
79 | interface = None
80 | resource = None
81 |
82 | return interface, resource, lambda: None
83 |
84 |
85 |
86 | realm = Realm()
87 |
88 | rootResource = Data(
89 | data=dedent(
90 | """
91 |
92 |
93 | Authentication tests
94 |
95 |
96 |
97 |
98 | - XML Directory Service
99 |
103 |
104 | - LDAP Directory Service
105 |
109 |
110 | - OpenDirectory Directory Service
111 |
115 |
116 |
117 |
118 |
119 | """[1:]
120 | ),
121 | type="text/html",
122 | )
123 |
124 |
125 | def addChild(name, method, service, credentialFactory=None):
126 | if method == "Basic":
127 | checker = UsernamePasswordCredentialChecker
128 | defaultCredentialFactory = BasicCredentialFactory
129 |
130 | elif method == "Digest":
131 | checker = HTTPDigestCredentialChecker
132 | defaultCredentialFactory = (
133 | lambda realmName: DigestCredentialFactory("md5", realmName)
134 | )
135 |
136 | if credentialFactory is None:
137 | credentialFactory = defaultCredentialFactory
138 |
139 | resourceName = "{0}{1}".format(name, method)
140 | print "Adding resource:", resourceName
141 |
142 | rootResource.putChild(
143 | resourceName,
144 | HTTPAuthSessionWrapper(
145 | Portal(realm, [checker(service)]),
146 | [credentialFactory("{0} {1} Realm".format(name, method))]
147 | )
148 | )
149 |
150 |
151 | xmlFileBasic = NamedTemporaryFile(delete=True)
152 | addChild("XML", "Basic", XMLDirectoryService(xmlFileBasic.name))
153 |
154 | xmlFileDigest = NamedTemporaryFile(delete=True)
155 | addChild("XML", "Digest", XMLDirectoryService(xmlFileDigest.name))
156 |
157 | if LDAPDirectoryService is not None:
158 | addChild("LDAP", "Basic", LDAPDirectoryService())
159 | addChild("LDAP", "Digest", LDAPDirectoryService())
160 |
161 | if OpenDirectoryDirectoryService is not None:
162 | addChild("OpenDirectory", "Basic", OpenDirectoryDirectoryService())
163 | addChild(
164 | "OpenDirectory", "Digest", OpenDirectoryDirectoryService(),
165 | credentialFactory=(
166 | lambda realmName: NoQOPDigestCredentialFactory("md5", realmName)
167 | )
168 | )
169 |
170 | resource = rootResource
171 |
--------------------------------------------------------------------------------
/twext/who/test/test_aggregate.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | """
18 | Aggregate directory service tests.
19 | """
20 |
21 | from twisted.python.components import proxyForInterface
22 | from twisted.trial import unittest
23 |
24 | from ..idirectory import IDirectoryService, DirectoryConfigurationError
25 | from ..directory import DirectoryRecord
26 | from ..aggregate import DirectoryService
27 | from ..util import ConstantsContainer
28 | from . import test_directory, test_xml
29 | from .test_xml import (
30 | BaseTest as XMLBaseTest,
31 | QueryMixIn, xmlService, TestService as XMLTestService,
32 | DirectoryServiceConvenienceTestMixIn,
33 | )
34 |
35 |
36 | class BaseTest(XMLBaseTest):
37 |
38 | def service(self, services=None):
39 | if services is None:
40 | services = (self.xmlService(),)
41 |
42 | #
43 | # Make sure aggregate DirectoryService isn't making
44 | # implementation assumptions about the IDirectoryService
45 | # objects it gets.
46 | #
47 | services = tuple((
48 | proxyForInterface(IDirectoryService)(s)
49 | for s in services
50 | ))
51 |
52 | class TestService(DirectoryService, QueryMixIn):
53 | pass
54 |
55 | return TestService(u"xyzzy", services)
56 |
57 | def xmlService(self, xmlData=None, serviceClass=None):
58 | return xmlService(
59 | self.mktemp(),
60 | xmlData=xmlData,
61 | serviceClass=serviceClass
62 | )
63 |
64 |
65 | class DirectoryServiceTest(
66 | unittest.TestCase,
67 | BaseTest,
68 | DirectoryServiceConvenienceTestMixIn,
69 | test_directory.BaseDirectoryServiceTest
70 | ):
71 | serviceClass = DirectoryService
72 | directoryRecordClass = DirectoryRecord
73 |
74 | def test_repr(self):
75 | service = self.service()
76 | self.assertEquals(repr(service), "")
77 |
78 |
79 | class DirectoryServiceQueryTest(
80 | unittest.TestCase,
81 | BaseTest,
82 | test_xml.DirectoryServiceQueryTestMixIn
83 | ):
84 | pass
85 |
86 |
87 | class DirectoryServiceImmutableTest(
88 | BaseTest,
89 | test_directory.BaseDirectoryServiceImmutableTest,
90 | ):
91 | serviceClass = DirectoryService
92 | directoryRecordClass = DirectoryRecord
93 |
94 |
95 | class AggregatedBaseTest(BaseTest):
96 |
97 | def service(self):
98 | class UsersDirectoryService(XMLTestService):
99 | recordType = ConstantsContainer((XMLTestService.recordType.user,))
100 |
101 | class GroupsDirectoryService(XMLTestService):
102 | recordType = ConstantsContainer((XMLTestService.recordType.group,))
103 |
104 | usersService = self.xmlService(
105 | xmlData=testXMLConfigUsers,
106 | serviceClass=UsersDirectoryService
107 | )
108 | groupsService = self.xmlService(
109 | xmlData=testXMLConfigGroups,
110 | serviceClass=GroupsDirectoryService
111 | )
112 |
113 | return BaseTest.service(
114 | self,
115 | services=(usersService, groupsService)
116 | )
117 |
118 |
119 | class DirectoryServiceAggregatedBaseTest(
120 | AggregatedBaseTest,
121 | DirectoryServiceTest,
122 | ):
123 | pass
124 |
125 |
126 | class DirectoryServiceAggregatedQueryTest(
127 | unittest.TestCase,
128 | AggregatedBaseTest,
129 | test_xml.DirectoryServiceQueryTestMixIn,
130 | ):
131 | pass
132 |
133 |
134 | class DirectoryServiceAggregatedImmutableTest(
135 | AggregatedBaseTest,
136 | test_directory.BaseDirectoryServiceImmutableTest,
137 | ):
138 | pass
139 |
140 |
141 | class DirectoryServiceTests(BaseTest, unittest.TestCase):
142 |
143 | def test_conflictingRecordTypes(self):
144 | self.assertRaises(
145 | DirectoryConfigurationError,
146 | self.service,
147 | services=(self.xmlService(), self.xmlService(testXMLConfigUsers)),
148 | )
149 |
150 |
151 | testXMLConfigUsers = """
152 |
153 |
154 |
155 |
156 | __wsanchez__
157 | wsanchez
158 | wilfredo_sanchez
159 | Wilfredo Sanchez
160 | zehcnasw
161 | wsanchez@bitbucket.calendarserver.org
162 | wsanchez@devnull.twistedmatrix.com
163 |
164 |
165 |
166 | __glyph__
167 | glyph
168 | Glyph Lefkowitz
169 | hpylg
170 | glyph@bitbucket.calendarserver.org
171 | glyph@devnull.twistedmatrix.com
172 |
173 |
174 |
175 | __sagen__
176 | sagen
177 | Morgen Sagen
178 | negas
179 | sagen@bitbucket.calendarserver.org
180 | shared@example.com
181 |
182 |
183 |
184 | __cdaboo__
185 | cdaboo
186 | Cyrus Daboo
187 | suryc
188 | cdaboo@bitbucket.calendarserver.org
189 |
190 |
191 |
192 | __dre__
193 | A3B1158F-0564-4F5B-81E4-A89EA5FF81B0
194 | dre
195 | Andre LaBranche
196 | erd
197 | dre@bitbucket.calendarserver.org
198 | shared@example.com
199 |
200 |
201 |
202 | __exarkun__
203 | exarkun
204 | Jean-Paul Calderone
205 | nucraxe
206 | exarkun@devnull.twistedmatrix.com
207 |
208 |
209 |
210 | __dreid__
211 | dreid
212 | David Reid
213 | dierd
214 | dreid@devnull.twistedmatrix.com
215 |
216 |
217 |
218 | __joe__
219 | joe
220 | Joe Schmoe
221 | eoj
222 | joe@example.com
223 |
224 |
225 |
226 | __alyssa__
227 | alyssa
228 | Alyssa P. Hacker
229 | assyla
230 | alyssa@example.com
231 |
232 |
233 |
234 | """
235 |
236 |
237 | testXMLConfigGroups = """
238 |
239 |
240 |
241 |
242 | __calendar-dev__
243 | calendar-dev
244 | Calendar Server developers
245 | dev@bitbucket.calendarserver.org
246 | __wsanchez__
247 | __glyph__
248 | __sagen__
249 | __cdaboo__
250 | __dre__
251 |
252 |
253 |
254 | __twisted__
255 | twisted
256 | Twisted Matrix Laboratories
257 | hack@devnull.twistedmatrix.com
258 | __wsanchez__
259 | __glyph__
260 | __exarkun__
261 | __dreid__
262 | __dre__
263 |
264 |
265 |
266 | __developers__
267 | developers
268 | All Developers
269 | __calendar-dev__
270 | __twisted__
271 | __alyssa__
272 |
273 |
274 |
275 | """
276 |
--------------------------------------------------------------------------------
/twext/who/test/test_concurrency.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | from __future__ import print_function
18 | from twext.python.types import MappingProxyType
19 | from twext.who.directory import DirectoryRecord
20 | from twext.who.idirectory import RecordType, FieldName
21 | from twext.who.ldap._constants import LDAPAttribute, LDAPObjectClass
22 | from twext.who.ldap._service import DirectoryService as LDAPDirectoryService, \
23 | DEFAULT_FIELDNAME_ATTRIBUTE_MAP, RecordTypeSchema
24 | from twext.who.opendirectory._service import DirectoryService as ODDirectoryService
25 | from twisted.internet.defer import inlineCallbacks, DeferredList, returnValue
26 | from twisted.trial import unittest
27 | import time
28 |
29 | """
30 | Test the concurrency of DS implementations against real servers.
31 | """
32 |
33 | TEST_FIELDNAME_MAP = dict(DEFAULT_FIELDNAME_ATTRIBUTE_MAP)
34 | TEST_FIELDNAME_MAP[FieldName.uid] = (u"uid",)
35 |
36 | TEST_RECORDTYPE_SCHEMAS_OSX = MappingProxyType({
37 |
38 | RecordType.user: RecordTypeSchema(
39 | # cn=users
40 | relativeDN=u"cn={0}".format("users"),
41 |
42 | # (objectClass=inetOrgPerson)
43 | attributes=(
44 | (
45 | LDAPAttribute.objectClass.value,
46 | LDAPObjectClass.inetOrgPerson.value,
47 | ),
48 | ),
49 | ),
50 |
51 | RecordType.group: RecordTypeSchema(
52 | # cn=groups
53 | relativeDN=u"cn={0}".format("groups"),
54 |
55 | # (objectClass=groupOfNames)
56 | attributes=(
57 | (
58 | LDAPAttribute.objectClass.value,
59 | LDAPObjectClass.groupOfNames.value,
60 | ),
61 | ),
62 | ),
63 |
64 | })
65 |
66 | TEST_RECORDTYPE_SCHEMAS_OTHER = MappingProxyType({
67 |
68 | RecordType.user: RecordTypeSchema(
69 | # ou=person
70 | relativeDN=u"ou={0}".format("people"),
71 |
72 | # (objectClass=inetOrgPerson)
73 | attributes=(
74 | (
75 | LDAPAttribute.objectClass.value,
76 | LDAPObjectClass.inetOrgPerson.value,
77 | ),
78 | ),
79 | ),
80 |
81 | RecordType.group: RecordTypeSchema(
82 | # ou=groupOfNames
83 | relativeDN=u"ou={0}".format(LDAPObjectClass.groupOfNames.value),
84 |
85 | # (objectClass=groupOfNames)
86 | attributes=(
87 | (
88 | LDAPAttribute.objectClass.value,
89 | LDAPObjectClass.groupOfNames.value,
90 | ),
91 | ),
92 | ),
93 |
94 | })
95 |
96 |
97 | class DirectoryServiceConcurrencyTest(unittest.TestCase):
98 | """
99 | Tests for directory records.
100 | """
101 |
102 | @inlineCallbacks
103 | def _runTest(self, num_threads, multiple_services, service_maker, details, num_requests, do_auth):
104 |
105 | if multiple_services:
106 | services = [service_maker() for _ in range(num_threads)]
107 | else:
108 | services = [service_maker()] * num_threads
109 |
110 | # Warm up each service before starting timer
111 | for n, svc in enumerate(services):
112 | record = yield svc.recordWithShortName(RecordType.user, details["user"].format(n + 1))
113 | self.assertTrue(isinstance(record, DirectoryRecord))
114 |
115 | start = time.time()
116 | ctr = [0]
117 |
118 | @inlineCallbacks
119 | def _records(n):
120 | for _ in range(num_requests):
121 | record = yield services[n].recordWithShortName(RecordType.user, details["user"].format(n + 1))
122 | self.assertTrue(isinstance(record, DirectoryRecord))
123 | ctr[0] += 1
124 |
125 | @inlineCallbacks
126 | def _auth(n):
127 | record = yield services[n].recordWithShortName(RecordType.user, details["user"].format(n + 1))
128 | for _ in range(num_requests):
129 | result = yield record.verifyPlaintextPassword(details["pswd"].format(n + 1))
130 | self.assertTrue(result)
131 | ctr[0] += 1
132 |
133 | dl = []
134 | for i in range(num_threads):
135 | dl.append((_auth if do_auth else _records)(i))
136 |
137 | dl = DeferredList(dl)
138 | yield dl
139 |
140 | returnValue((time.time() - start, ctr[0],))
141 |
142 | @inlineCallbacks
143 | def test_ldap_multi_service(self):
144 | """
145 | See if {ldap._service.DirectoryService is concurrent.
146 | """
147 |
148 | num_threads = 20
149 | multiple_services = False
150 | num_requests = 100
151 | do_auth = False
152 | use_od = False
153 | configChoice = "local"
154 |
155 | configs = {
156 | "local": {
157 | "url": "ldap://localhost",
158 | "baseDN": "dc=example,dc=com",
159 | "rschema": TEST_RECORDTYPE_SCHEMAS_OSX,
160 | "user": u"user{:02d}",
161 | "pswd": u"user{:02d}",
162 | },
163 | "example": {
164 | "url": "ldap://example.com",
165 | "baseDN": "o=example.com,o=email",
166 | "rschema": TEST_RECORDTYPE_SCHEMAS_OTHER,
167 | "user": u"TestAccount{}",
168 | "pswd": u"pswd",
169 | },
170 | }
171 |
172 | details = configs[configChoice]
173 |
174 | def _serviceMaker():
175 | if use_od:
176 | return ODDirectoryService(
177 | nodeName="/LDAPv3/127.0.0.1",
178 | )
179 | else:
180 | return LDAPDirectoryService(
181 | url=details["url"],
182 | baseDN=details["baseDN"],
183 | fieldNameToAttributesMap=TEST_FIELDNAME_MAP,
184 | recordTypeSchemas=details["rschema"],
185 | threadPoolMax=20,
186 | )
187 |
188 | duration, count = yield self._runTest(num_threads, multiple_services, _serviceMaker, details, num_requests, do_auth)
189 |
190 | print(
191 | "\n\nType: {} {} {}\nNumber of Services/Requests: {}/{}\nTime: {}\nCount: {}\n".format(
192 | "OD" if use_od else "LDAP",
193 | "Multiple" if multiple_services else "Single",
194 | "Auth" if do_auth else "query",
195 | num_threads,
196 | num_requests,
197 | duration,
198 | count,
199 | )
200 | )
201 |
202 | test_ldap_multi_service.skip = "Not really a unit test - requires actually server to work"
203 |
--------------------------------------------------------------------------------
/twext/who/util.py:
--------------------------------------------------------------------------------
1 | # -*- test-case-name: twext.who.test.test_util -*-
2 | ##
3 | # Copyright (c) 2013-2017 Apple Inc. All rights reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | ##
17 |
18 | """
19 | Directory service module utilities.
20 | """
21 |
22 | __all__ = [
23 | "ConstantsContainer",
24 | "uniqueResult",
25 | "describe",
26 | ]
27 |
28 | from inspect import getmembers, isclass, isfunction
29 |
30 | from twisted.python.constants import (
31 | Names, Values, Flags, NamedConstant, ValueConstant, FlagConstant,
32 | )
33 |
34 | from .idirectory import DirectoryServiceError
35 |
36 |
37 | class ConstantsContainer(object):
38 | """
39 | A container for constants.
40 | """
41 |
42 | def __init__(self, sources):
43 | self._constants = {}
44 | self._methods = {}
45 |
46 | for source in sources:
47 | if isclass(source):
48 | if issubclass(source, CONTAINER_CLASSES):
49 | self._addConstants(source.iterconstants())
50 | self._addMethods(getmembers(source, isfunction))
51 | else:
52 | raise TypeError(
53 | "Unknown constants type: {0}".format(source)
54 | )
55 |
56 | elif isinstance(source, ConstantsContainer):
57 | self._addConstants(source.iterconstants())
58 | self._addMethods(source._methods.iteritems())
59 |
60 | elif isinstance(source, CONSTANT_CLASSES):
61 | self._addConstants((source,))
62 |
63 | else:
64 | self._addConstants(source)
65 |
66 | def _addConstants(self, constants):
67 | for constant in constants:
68 | if hasattr(self, "_constantsClass"):
69 | if constant.__class__ != self._constantsClass:
70 | raise TypeError(
71 | "Can't mix constants classes in the "
72 | "same constants container: {0} != {1}"
73 | .format(constant.__class__, self._constantsClass)
74 | )
75 | else:
76 | self._constantsClass = constant.__class__
77 |
78 | if issubclass(self._constantsClass, ValueConstant):
79 | self.lookupByValue = self._lookupByValue
80 |
81 | if (
82 | constant.name in self._constants and
83 | self._constants[constant.name] is not constant
84 | ):
85 | raise ValueError("Name conflict: {0}".format(constant.name))
86 |
87 | self._constants[constant.name] = constant
88 |
89 | def _addMethods(self, methods):
90 | for name, method in methods:
91 | if name[0] == "_":
92 | continue
93 |
94 | if (
95 | name in self._constants or
96 | (name in self._methods and self._methods[name] is not method)
97 | ):
98 | raise ValueError("Name conflict: {0}".format(name))
99 |
100 | self._methods[name] = method
101 |
102 | def __getattr__(self, name):
103 | attr = self._constants.get(name, None)
104 | if attr is not None:
105 | return attr
106 |
107 | attr = self._methods.get(name, None)
108 | if attr is not None:
109 | return attr
110 |
111 | raise AttributeError(name)
112 |
113 | def iterconstants(self):
114 | return self._constants.itervalues()
115 |
116 | def lookupByName(self, name):
117 | try:
118 | return self._constants[name]
119 | except KeyError:
120 | raise ValueError(name)
121 |
122 | def _lookupByValue(self, value):
123 | for constant in self.iterconstants():
124 | if constant.value == value:
125 | return constant
126 | raise ValueError(value)
127 |
128 |
129 | def uniqueResult(values):
130 | result = None
131 | for value in values:
132 | if result is None:
133 | result = value
134 | else:
135 | raise DirectoryServiceError(
136 | "Multiple values found where one expected."
137 | )
138 | return result
139 |
140 |
141 | def firstResult(values):
142 | for value in values:
143 | return value
144 | return None
145 |
146 |
147 | def describe(constant):
148 | if isinstance(constant, FlagConstant):
149 | return "|".join(
150 | sorted(getattr(flag, "description", flag.name) for flag in constant)
151 | )
152 |
153 | if isinstance(constant, (NamedConstant, ValueConstant)):
154 | for attr in ("description", "name"):
155 | description = getattr(constant, attr, None)
156 | if description is not None:
157 | return description
158 |
159 | return unicode(constant)
160 |
161 |
162 | CONTAINER_CLASSES = (Names, Values, Flags)
163 | CONSTANT_CLASSES = (NamedConstant, ValueConstant, FlagConstant)
164 |
--------------------------------------------------------------------------------
/twisted/plugins/masterchild.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Copyright (c) 2010-2017 Apple Inc. All rights reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | ##
16 |
17 | from zope.interface import implementer
18 |
19 | from twisted.python.reflect import namedClass
20 | from twisted.plugin import IPlugin
21 | from twisted.application.service import IServiceMaker
22 |
23 |
24 | @implementer(IPlugin, IServiceMaker)
25 | class ServiceMakerWrapper(object):
26 | """
27 | ServiceMaker that instantiates and wraps a ServiceMaker, given a class name
28 | and arguments.
29 | """
30 |
31 | def __init__(self, className, *args, **kwargs):
32 | """
33 | @param className: The fully qualified name of the
34 | L{IServiceMaker}-providing class to instantiate.
35 | @type className: L{str}
36 |
37 | @param args: Sequential arguments to pass to the class's constructor.
38 | @type args: arguments L{list}
39 |
40 | @param kwargs: Keyword arguments to pass to the class's constructor.
41 | @type args: arguments L{dict}
42 | """
43 | self.className = className
44 | self.args = args
45 | self.kwargs = kwargs
46 |
47 | @property
48 | def wrappedServiceMaker(self):
49 | if not hasattr(self, "_wrappedServiceMaker"):
50 | makerClass = namedClass(self.className)
51 | maker = makerClass(*self.args, **self.kwargs)
52 | self._wrappedServiceMaker = maker
53 |
54 | return self._wrappedServiceMaker
55 |
56 | @property
57 | def tapname(self):
58 | return self.wrappedServiceMaker.tapname
59 |
60 | @property
61 | def description(self):
62 | return self.wrappedServiceMaker.description
63 |
64 | @property
65 | def options(self):
66 | return self.wrappedServiceMaker.options
67 |
68 | def makeService(self, options):
69 | return self.wrappedServiceMaker.makeService(options)
70 |
71 |
72 | masterServiceMaker = ServiceMakerWrapper(
73 | "twext.application.masterchild.MasterServiceMaker"
74 | )
75 |
76 | childServiceMaker = ServiceMakerWrapper(
77 | "twext.application.masterchild.ChildServiceMaker"
78 | )
79 |
--------------------------------------------------------------------------------