mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #3111 from RasmusWL/python-fabric-command-injection
Approved by BekaValentine
This commit is contained in:
@@ -15,6 +15,7 @@ Support for Django version 2.x and 3.x
|
|||||||
|
|
||||||
| **Query** | **Expected impact** | **Change** |
|
| **Query** | **Expected impact** | **Change** |
|
||||||
|----------------------------|------------------------|------------------------------------------------------------------|
|
|----------------------------|------------------------|------------------------------------------------------------------|
|
||||||
|
| Uncontrolled command line (`py/command-line-injection`) | More results | We now model the `fabric` and `invoke` pacakges for command execution. |
|
||||||
|
|
||||||
### Web framework support
|
### Web framework support
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSink(TaintTracking::Sink sink) {
|
override predicate isSink(TaintTracking::Sink sink) {
|
||||||
sink instanceof OsCommandFirstArgument or
|
sink instanceof CommandSink
|
||||||
sink instanceof ShellCommand
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isExtension(TaintTracking::Extension extension) {
|
override predicate isExtension(TaintTracking::Extension extension) {
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
/** Provides class and predicates to track external data that
|
/**
|
||||||
|
* Provides class and predicates to track external data that
|
||||||
* may represent malicious OS commands.
|
* may represent malicious OS commands.
|
||||||
*
|
*
|
||||||
* This module is intended to be imported into a taint-tracking query
|
* This module is intended to be imported into a taint-tracking query
|
||||||
* to extend `TaintKind` and `TaintSink`.
|
* to extend `TaintKind` and `TaintSink`.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
import python
|
|
||||||
|
|
||||||
|
import python
|
||||||
import semmle.python.security.TaintTracking
|
import semmle.python.security.TaintTracking
|
||||||
import semmle.python.security.strings.Untrusted
|
import semmle.python.security.strings.Untrusted
|
||||||
|
|
||||||
|
/** Abstract taint sink that is potentially vulnerable to malicious shell commands. */
|
||||||
|
abstract class CommandSink extends TaintSink { }
|
||||||
|
|
||||||
private ModuleObject osOrPopenModule() {
|
private ModuleObject osOrPopenModule() {
|
||||||
result.getName() = "os" or
|
result.getName() = "os" or
|
||||||
@@ -17,10 +19,9 @@ private ModuleObject osOrPopenModule() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Object makeOsCall() {
|
private Object makeOsCall() {
|
||||||
exists(string name |
|
exists(string name | result = ModuleObject::named("subprocess").attr(name) |
|
||||||
result = ModuleObject::named("subprocess").attr(name) |
|
|
||||||
name = "Popen" or
|
name = "Popen" or
|
||||||
name = "call" or
|
name = "call" or
|
||||||
name = "check_call" or
|
name = "check_call" or
|
||||||
name = "check_output" or
|
name = "check_output" or
|
||||||
name = "run"
|
name = "run"
|
||||||
@@ -29,40 +30,27 @@ private Object makeOsCall() {
|
|||||||
|
|
||||||
/**Special case for first element in sequence. */
|
/**Special case for first element in sequence. */
|
||||||
class FirstElementKind extends TaintKind {
|
class FirstElementKind extends TaintKind {
|
||||||
|
FirstElementKind() { this = "sequence[" + any(ExternalStringKind key) + "][0]" }
|
||||||
|
|
||||||
FirstElementKind() {
|
override string repr() { result = "first item in sequence of " + this.getItem().repr() }
|
||||||
this = "sequence[" + any(ExternalStringKind key) + "][0]"
|
|
||||||
}
|
|
||||||
|
|
||||||
override string repr() {
|
|
||||||
result = "first item in sequence of " + this.getItem().repr()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets the taint kind for item in this sequence. */
|
/** Gets the taint kind for item in this sequence. */
|
||||||
ExternalStringKind getItem() {
|
ExternalStringKind getItem() { this = "sequence[" + result + "][0]" }
|
||||||
this = "sequence[" + result + "][0]"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FirstElementFlow extends DataFlowExtension::DataFlowNode {
|
class FirstElementFlow extends DataFlowExtension::DataFlowNode {
|
||||||
|
FirstElementFlow() { this = any(SequenceNode s).getElement(0) }
|
||||||
|
|
||||||
FirstElementFlow() {
|
override ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) {
|
||||||
this = any(SequenceNode s).getElement(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override
|
|
||||||
ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) {
|
|
||||||
result.(SequenceNode).getElement(0) = this and tokind.(FirstElementKind).getItem() = fromkind
|
result.(SequenceNode).getElement(0) = this and tokind.(FirstElementKind).getItem() = fromkind
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A taint sink that is potentially vulnerable to malicious shell commands.
|
/**
|
||||||
|
* A taint sink that is potentially vulnerable to malicious shell commands.
|
||||||
* The `vuln` in `subprocess.call(shell=vuln)` and similar calls.
|
* The `vuln` in `subprocess.call(shell=vuln)` and similar calls.
|
||||||
*/
|
*/
|
||||||
class ShellCommand extends TaintSink {
|
class ShellCommand extends CommandSink {
|
||||||
|
|
||||||
override string toString() { result = "shell command" }
|
override string toString() { result = "shell command" }
|
||||||
|
|
||||||
ShellCommand() {
|
ShellCommand() {
|
||||||
@@ -75,7 +63,8 @@ class ShellCommand extends TaintSink {
|
|||||||
or
|
or
|
||||||
exists(CallNode call, string name |
|
exists(CallNode call, string name |
|
||||||
call.getAnArg() = this and
|
call.getAnArg() = this and
|
||||||
call.getFunction().refersTo(osOrPopenModule().attr(name)) |
|
call.getFunction().refersTo(osOrPopenModule().attr(name))
|
||||||
|
|
|
||||||
name = "system" or
|
name = "system" or
|
||||||
name = "popen" or
|
name = "popen" or
|
||||||
name.matches("popen_")
|
name.matches("popen_")
|
||||||
@@ -94,19 +83,18 @@ class ShellCommand extends TaintSink {
|
|||||||
/* List (or tuple) containing a tainted string command */
|
/* List (or tuple) containing a tainted string command */
|
||||||
kind instanceof ExternalStringSequenceKind
|
kind instanceof ExternalStringSequenceKind
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A taint sink that is potentially vulnerable to malicious shell commands.
|
/**
|
||||||
|
* A taint sink that is potentially vulnerable to malicious shell commands.
|
||||||
* The `vuln` in `subprocess.call(vuln, ...)` and similar calls.
|
* The `vuln` in `subprocess.call(vuln, ...)` and similar calls.
|
||||||
*/
|
*/
|
||||||
class OsCommandFirstArgument extends TaintSink {
|
class OsCommandFirstArgument extends CommandSink {
|
||||||
|
|
||||||
override string toString() { result = "OS command first argument" }
|
override string toString() { result = "OS command first argument" }
|
||||||
|
|
||||||
OsCommandFirstArgument() {
|
OsCommandFirstArgument() {
|
||||||
not this instanceof ShellCommand and
|
not this instanceof ShellCommand and
|
||||||
exists(CallNode call|
|
exists(CallNode call |
|
||||||
call.getFunction().refersTo(makeOsCall()) and
|
call.getFunction().refersTo(makeOsCall()) and
|
||||||
call.getArg(0) = this
|
call.getArg(0) = this
|
||||||
)
|
)
|
||||||
@@ -119,5 +107,127 @@ class OsCommandFirstArgument extends TaintSink {
|
|||||||
/* List (or tuple) whose first element is tainted */
|
/* List (or tuple) whose first element is tainted */
|
||||||
kind instanceof FirstElementKind
|
kind instanceof FirstElementKind
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
// Modeling of the 'invoke' package and 'fabric' package (v 2.x)
|
||||||
|
//
|
||||||
|
// Since fabric build so closely upon invoke, we model them together to avoid
|
||||||
|
// duplication
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
/**
|
||||||
|
* A taint sink that is potentially vulnerable to malicious shell commands.
|
||||||
|
* The `vuln` in `invoke.run(vuln, ...)` and similar calls.
|
||||||
|
*/
|
||||||
|
class InvokeRun extends CommandSink {
|
||||||
|
InvokeRun() {
|
||||||
|
this = Value::named("invoke.run").(FunctionValue).getArgumentForCall(_, 0)
|
||||||
|
or
|
||||||
|
this = Value::named("invoke.sudo").(FunctionValue).getArgumentForCall(_, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string toString() { result = "InvokeRun" }
|
||||||
|
|
||||||
|
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal TaintKind to track the invoke.Context instance passed to functions
|
||||||
|
* marked with @invoke.task
|
||||||
|
*/
|
||||||
|
private class InvokeContextArg extends TaintKind {
|
||||||
|
InvokeContextArg() { this = "InvokeContextArg" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Internal TaintSource to track the context passed to functions marked with @invoke.task */
|
||||||
|
private class InvokeContextArgSource extends TaintSource {
|
||||||
|
InvokeContextArgSource() {
|
||||||
|
exists(Function f, Expr decorator |
|
||||||
|
count(f.getADecorator()) = 1 and
|
||||||
|
(
|
||||||
|
decorator = f.getADecorator() and not decorator instanceof Call
|
||||||
|
or
|
||||||
|
decorator = f.getADecorator().(Call).getFunc()
|
||||||
|
) and
|
||||||
|
(
|
||||||
|
decorator.pointsTo(Value::named("invoke.task"))
|
||||||
|
or
|
||||||
|
decorator.pointsTo(Value::named("fabric.task"))
|
||||||
|
)
|
||||||
|
|
|
||||||
|
this.(ControlFlowNode).getNode() = f.getArg(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate isSourceOf(TaintKind kind) { kind instanceof InvokeContextArg }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint sink that is potentially vulnerable to malicious shell commands.
|
||||||
|
* The `vuln` in `invoke.Context().run(vuln, ...)` and similar calls.
|
||||||
|
*/
|
||||||
|
class InvokeContextRun extends CommandSink {
|
||||||
|
InvokeContextRun() {
|
||||||
|
exists(CallNode call |
|
||||||
|
any(InvokeContextArg k).taints(call.getFunction().(AttrNode).getObject("run"))
|
||||||
|
or
|
||||||
|
call = Value::named("invoke.Context").(ClassValue).lookup("run").getACall()
|
||||||
|
or
|
||||||
|
// fabric.connection.Connection is a subtype of invoke.context.Context
|
||||||
|
// since fabric.Connection.run has a decorator, it doesn't work with FunctionValue :|
|
||||||
|
// and `Value::named("fabric.Connection").(ClassValue).lookup("run").getACall()` returned no results,
|
||||||
|
// so here is the hacky solution that works :\
|
||||||
|
call.getFunction().(AttrNode).getObject("run").pointsTo().getClass() =
|
||||||
|
Value::named("fabric.Connection")
|
||||||
|
|
|
||||||
|
this = call.getArg(0)
|
||||||
|
or
|
||||||
|
this = call.getArgByName("command")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string toString() { result = "InvokeContextRun" }
|
||||||
|
|
||||||
|
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint sink that is potentially vulnerable to malicious shell commands.
|
||||||
|
* The `vuln` in `fabric.Group().run(vuln, ...)` and similar calls.
|
||||||
|
*/
|
||||||
|
class FabricGroupRun extends CommandSink {
|
||||||
|
FabricGroupRun() {
|
||||||
|
exists(ClassValue cls |
|
||||||
|
cls.getASuperType() = Value::named("fabric.Group") and
|
||||||
|
this = cls.lookup("run").(FunctionValue).getArgumentForCall(_, 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string toString() { result = "FabricGroupRun" }
|
||||||
|
|
||||||
|
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
// Modeling of the 'invoke' package and 'fabric' package (v 1.x)
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
class FabricV1Commands extends CommandSink {
|
||||||
|
FabricV1Commands() {
|
||||||
|
// since `run` and `sudo` are decorated, we can't use FunctionValue's :(
|
||||||
|
exists(CallNode call |
|
||||||
|
call = Value::named("fabric.api.local").getACall()
|
||||||
|
or
|
||||||
|
call = Value::named("fabric.api.run").getACall()
|
||||||
|
or
|
||||||
|
call = Value::named("fabric.api.sudo").getACall()
|
||||||
|
|
|
||||||
|
this = call.getArg(0)
|
||||||
|
or
|
||||||
|
this = call.getArgByName("command")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string toString() { result = "FabricV1Commands" }
|
||||||
|
|
||||||
|
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
| fabric_v1_test.py:8:7:8:28 | FabricV1Commands | externally controlled string |
|
||||||
|
| fabric_v1_test.py:9:5:9:27 | FabricV1Commands | externally controlled string |
|
||||||
|
| fabric_v1_test.py:10:6:10:38 | FabricV1Commands | externally controlled string |
|
||||||
|
| fabric_v2_test.py:10:16:10:25 | InvokeContextRun | externally controlled string |
|
||||||
|
| fabric_v2_test.py:12:15:12:36 | InvokeContextRun | externally controlled string |
|
||||||
|
| fabric_v2_test.py:16:45:16:54 | FabricGroupRun | externally controlled string |
|
||||||
|
| fabric_v2_test.py:21:10:21:13 | FabricGroupRun | externally controlled string |
|
||||||
|
| fabric_v2_test.py:31:14:31:41 | InvokeContextRun | externally controlled string |
|
||||||
|
| fabric_v2_test.py:33:15:33:64 | InvokeContextRun | externally controlled string |
|
||||||
|
| invoke_test.py:8:12:8:21 | InvokeRun | externally controlled string |
|
||||||
|
| invoke_test.py:9:20:9:40 | InvokeRun | externally controlled string |
|
||||||
|
| invoke_test.py:12:17:12:24 | InvokeRun | externally controlled string |
|
||||||
|
| invoke_test.py:13:25:13:32 | InvokeRun | externally controlled string |
|
||||||
|
| invoke_test.py:17:11:17:40 | InvokeContextRun | externally controlled string |
|
||||||
|
| invoke_test.py:21:11:21:32 | InvokeContextRun | externally controlled string |
|
||||||
|
| invoke_test.py:27:11:27:25 | InvokeContextRun | externally controlled string |
|
||||||
|
| invoke_test.py:32:11:32:25 | InvokeContextRun | externally controlled string |
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import python
|
||||||
|
import semmle.python.security.injection.Command
|
||||||
|
import semmle.python.security.strings.Untrusted
|
||||||
|
|
||||||
|
from CommandSink sink, TaintKind kind
|
||||||
|
where sink.sinks(kind)
|
||||||
|
select sink, kind
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2020 Jeff Forcier.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""tests for the 'fabric' package (v1.x)
|
||||||
|
|
||||||
|
See http://docs.fabfile.org/en/1.14/tutorial.html
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fabric.api import run, local, sudo
|
||||||
|
|
||||||
|
local('echo local execution')
|
||||||
|
run('echo remote execution')
|
||||||
|
sudo('echo remote execution with sudo')
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""tests for the 'fabric' package (v2.x)
|
||||||
|
|
||||||
|
Most of these examples are taken from the fabric documentation: http://docs.fabfile.org/en/2.5/getting-started.html
|
||||||
|
See fabric-LICENSE for its' license.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fabric import Connection
|
||||||
|
|
||||||
|
c = Connection('web1')
|
||||||
|
result = c.run('uname -s')
|
||||||
|
|
||||||
|
c.run(command='echo run with kwargs')
|
||||||
|
|
||||||
|
|
||||||
|
from fabric import SerialGroup as Group
|
||||||
|
results = Group('web1', 'web2', 'mac1').run('uname -s')
|
||||||
|
|
||||||
|
|
||||||
|
from fabric import SerialGroup as Group
|
||||||
|
pool = Group('web1', 'web2', 'web3')
|
||||||
|
pool.run('ls')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# using the 'fab' command-line tool
|
||||||
|
|
||||||
|
from fabric import task
|
||||||
|
|
||||||
|
@task
|
||||||
|
def upload_and_unpack(c):
|
||||||
|
if c.run('test -f /opt/mydata/myfile', warn=True).failed:
|
||||||
|
c.put('myfiles.tgz', '/opt/mydata')
|
||||||
|
c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""tests for the 'invoke' package
|
||||||
|
|
||||||
|
see https://www.pyinvoke.org/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import invoke
|
||||||
|
|
||||||
|
invoke.run('echo run')
|
||||||
|
invoke.run(command='echo run with kwarg')
|
||||||
|
|
||||||
|
def with_sudo():
|
||||||
|
invoke.sudo('whoami')
|
||||||
|
invoke.sudo(command='whoami')
|
||||||
|
|
||||||
|
def manual_context():
|
||||||
|
c = invoke.Context()
|
||||||
|
c.run('echo run from manual context')
|
||||||
|
manual_context()
|
||||||
|
|
||||||
|
def foo_helper(c):
|
||||||
|
c.run('echo from foo_helper')
|
||||||
|
|
||||||
|
# for use with the 'invoke' command-line tool
|
||||||
|
@invoke.task
|
||||||
|
def foo(c):
|
||||||
|
# 'c' is a invoke.context.Context
|
||||||
|
c.run('echo task foo')
|
||||||
|
foo_helper(c)
|
||||||
|
|
||||||
|
@invoke.task()
|
||||||
|
def bar(c):
|
||||||
|
c.run('echo task bar')
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .connection import Connection
|
||||||
|
from .group import Group, SerialGroup, ThreadingGroup
|
||||||
|
from .tasks import task
|
||||||
25
python/ql/test/query-tests/Security/lib/fabric/api.py
Normal file
25
python/ql/test/query-tests/Security/lib/fabric/api.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# For the 1.x version
|
||||||
|
|
||||||
|
def needs_host(func):
|
||||||
|
@wraps(func)
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
def local(command, capture=False, shell=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@needs_host
|
||||||
|
def run(command, shell=True, pty=True, combine_stderr=None, quiet=False,
|
||||||
|
warn_only=False, stdout=None, stderr=None, timeout=None, shell_escape=None,
|
||||||
|
capture_buffer_size=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@needs_host
|
||||||
|
def sudo(command, shell=True, pty=True, combine_stderr=None, user=None,
|
||||||
|
quiet=False, warn_only=False, stdout=None, stderr=None, group=None,
|
||||||
|
timeout=None, shell_escape=None, capture_buffer_size=None):
|
||||||
|
pass
|
||||||
15
python/ql/test/query-tests/Security/lib/fabric/connection.py
Normal file
15
python/ql/test/query-tests/Security/lib/fabric/connection.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from invoke import Context
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def opens(method, self, *args, **kwargs):
|
||||||
|
self.open()
|
||||||
|
return method(self, *args, **kwargs)
|
||||||
|
|
||||||
|
class Connection(Context):
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@opens
|
||||||
|
def run(self, command, **kwargs):
|
||||||
|
pass
|
||||||
11
python/ql/test/query-tests/Security/lib/fabric/group.py
Normal file
11
python/ql/test/query-tests/Security/lib/fabric/group.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class Group(list):
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class SerialGroup(Group):
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ThreadingGroup(Group):
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
2
python/ql/test/query-tests/Security/lib/fabric/tasks.py
Normal file
2
python/ql/test/query-tests/Security/lib/fabric/tasks.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
def task(*args, **kwargs):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from .context import Context
|
||||||
|
from .tasks import task
|
||||||
|
|
||||||
|
def run(command, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sudo(command, **kwargs):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
class Context(object):
|
||||||
|
def run(self, command, **kwargs):
|
||||||
|
pass
|
||||||
2
python/ql/test/query-tests/Security/lib/invoke/tasks.py
Normal file
2
python/ql/test/query-tests/Security/lib/invoke/tasks.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
def task(*args, **kwargs):
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user