added auth, request parser, sql_tests

This commit is contained in:
Nathan Buckner 2015-07-07 23:24:59 -05:00
parent 19598a404b
commit 3d3b3f44ee
14 changed files with 937 additions and 51 deletions

View File

@ -0,0 +1,28 @@
_syntribos()
{
local cur prev opts
COMPREPLY=()
cur=$(echo "${COMP_WORDS[COMP_CWORD]}"|sed 's/\\/\\\\/g')
prev=$(echo "${COMP_WORDS[COMP_CWORD-1]}"|sed 's/\\/\\\\/g')
if [[ ${COMP_CWORD} == 1 ]]; then
opts=$(python -c "from cafe.drivers.unittest.autocomplete import print_configs;print_configs()" 2>/dev/null)
elif [[ ${COMP_CWORD} == 2 ]]; then
COMPREPLY=( $(compgen -o filenames -A file "$cur") )
elif [[ ${cur} == -* ]]; then
opts="--help --test-types"
else
opts=$(python -c "from syntribos.runner import Runner;Runner.populate_tests();from syntribos.tests.base import test_table;from pprint import pprint;[pprint(i) for i in test_table.keys()]" 2>/dev/null)
fi
if [[ ${COMP_CWORD} -ne 2 ]]; then
opts=$(echo $opts|sed 's/\\/\\\\/g')
COMPREPLY=( $(compgen -W '${opts}' -- ${cur}) )
COMPREPLY="${COMPREPLY} "
fi
#sed for windows backslash
return 0
}
complete -o nospace -F _syntribos syntribos

View File

@ -1,5 +1,5 @@
"""
Copyright 2013 Rackspace
Copyright 2015 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,7 +18,7 @@ from setuptools import setup, find_packages
requires = open('pip-requires').readlines()
setup(
name='apiscan',
name='syntribos',
version='0.0.1',
description=('API Security Scanner'),
long_description='{0}\n\n{1}'.format(
@ -31,6 +31,8 @@ setup(
install_requires=requires,
license=open('LICENSE').read(),
zip_safe=False,
entry_points={'console_scripts': [
'syntribos = syntribos.runner:entry_point']},
classifiers=(
'Development Status :: 1 - Planning',
'Intended Audience :: Developers',

View File

@ -1,92 +1,84 @@
import json
from xml.etree import ElementTree
from cafe.drivers.unittest.datasets import DatasetList
from cafe.engine.behaviors import BaseBehavior
class DynamicDataGen(DatasetList):
strings = None
def __init__(self, name, data, ignore_var):
for stri in self.strings:
ctr = 0
class FuzzBehavior(BaseBehavior):
@classmethod
def fuzz_body(cls, strings, data, skip_var, name):
for str_num, stri in enumerate(strings, 1):
if isinstance(data, dict):
for dic in self._build_combinations(
name, stri, data, ignore_var):
self.append_new_dataset(
"{0}-{1}-{2}".format(self.test_name, name, ctr), {
"model": json.dumps(dic).replace(
ignore_var, "")})
ctr += 1
model_iter = cls._build_combinations(stri, data, skip_var)
elif isinstance(data, ElementTree.Element):
for element in self._build_xml_combinations(
name, stri, data, ignore_var):
self.append_new_dataset(
"{0}-{1}-{2}".format(self.test_name, name, ctr), {
"model": ElementTree.tostring(element).replace(
ignore_var, "")})
ctr += 1
model_iter = cls._build_xml_combinations(stri, data, skip_var)
else:
raise TypeError("Format not recognized!")
for model_num, model in enumerate(model_iter, 1):
name = "{0}_str{1}_model{2}".format(name, str_num, model_num)
if isinstance(dict):
string = json.dumps(model)
else:
string = ElementTree.tostring(model)
string = string.replace(skip_var, "")
yield (name, string)
def _build_combinations(self, name, stri, dic, ignore_var):
@classmethod
def _build_combinations(cls, stri, dic, skip_var):
for key, val in dic.iteritems():
if ignore_var in key:
if skip_var in key:
continue
elif isinstance(val, dict):
for ret in self._build_combinations(
name, stri, val, ignore_var):
yield self._merge_dictionaries(dic, {key: ret})
for ret in cls._build_combinations(stri, val, skip_var):
yield cls._merge_dictionaries(dic, {key: ret})
elif isinstance(val, list):
for i, v in enumerate(val):
list_ = [_ for _ in val]
if isinstance(v, dict):
for ret in self._build_combinations(
name, stri, v, ignore_var):
for ret in cls._build_combinations(stri, v, skip_var):
list_[i] = ret.copy()
yield self._merge_dictionaries(dic, {key: list_})
yield cls._merge_dictionaries(dic, {key: list_})
else:
list_[i] = stri
yield self._merge_dictionaries(dic, {key: list_})
yield cls._merge_dictionaries(dic, {key: list_})
else:
yield self._merge_dictionaries(dic, {key: stri})
yield cls._merge_dictionaries(dic, {key: stri})
def _merge_dictionaries(self, x, y):
@staticmethod
def _merge_dictionaries(x, y):
z = x.copy()
z.update(y)
return z
def _build_xml_combinations(self, name, stri, ele, ignore_var):
if ignore_var not in ele.tag:
@classmethod
def _build_xml_combinations(cls, stri, ele, skip_var):
if skip_var not in ele.tag:
if ele.text is not None:
yield self._update_element(ele, stri)
for attr in self._build_combinations(
name, stri, ele.attrib, ignore_var):
yield self._update_attribs(ele, attr)
yield cls._update_element(ele, stri)
for attr in cls._build_combinations(stri, ele.attrib, skip_var):
yield cls._update_attribs(ele, attr)
for i, element in enumerate(list(ele)):
for ret in self._build_xml_combinations(
name, stri, element, ignore_var):
for ret in cls._build_xml_combinations(
stri, element, skip_var):
list_ = [_ for _ in ele.getchildren()]
list_[i] = ret.copy()
yield self._update_inner_element(ele, list_)
yield cls._update_inner_element(ele, list_)
def _update_element(self, ele, stri):
@staticmethod
def _update_element(ele, stri):
ret = ele.copy()
ret.text = stri
return ret
def _update_attribs(self, ele, attribs):
@staticmethod
def _update_attribs(ele, attribs):
ret = ele.copy()
ret.attrib = attribs
return ret
def _update_inner_element(self, ele, list_):
@staticmethod
def _update_inner_element(ele, list_):
ret = ele.copy()
for i, v in enumerate(list_):
ret[i] = v
return ret
class ExampleDynamicDataGen(DynamicDataGen):
strings = ["abc", "def"]
test_name = "Example"

View File

View File

@ -0,0 +1,73 @@
from cafe.engine.behaviors import BaseBehavior
from cafe.engine.http.client import AutoMarshallingHTTPClient
from syntribos.extensions.rax_auth.models import auth_models
from syntribos.extensions.rax_auth.auth_config import (
UserAuthConfig, UserConfig)
class TokensBehavior(BaseBehavior):
@classmethod
def get_access_data_config(cls, user_config, userauth_config):
return cls.get_access_data(
url=user_config.endpoint or userauth_config.endpoint,
username=user_config.username,
password=user_config.password,
tenant_name=user_config.tenant_name,
tenant_id=user_config.tenant_id,
token=user_config.token,
api_key=user_config.api_key,
serialize_format=userauth_config.serialize_format,
deserialize_format=userauth_config.deserialize_format)
@classmethod
def get_access_data(cls, *args, **kwargs):
return cls.authenticate(*args, **kwargs).entity
@classmethod
def authenticate(
cls, url, username=None, password=None, tenant_name=None,
tenant_id=None, token=None, api_key=None, rsa_key=None,
domain=None, serialize_format="json", deserialize_format="json"):
url = '{0}/tokens'.format(url)
client = AutoMarshallingHTTPClient(
serialize_format, deserialize_format)
client.default_headers["Content-Type"] = "application/{0}".format(
serialize_format)
client.default_headers["Accept"] = "application/{0}".format(
deserialize_format)
kwargs = {}
kwargs["tenant_name"] = tenant_name
kwargs["tenant_id"] = tenant_id
if password is not None:
kwargs["password_creds"] = auth_models.PasswordCredentials(
username=username, password=password)
if token is not None:
kwargs["token_creds"] = auth_models.Token(id_=token)
if api_key is not None:
kwargs["api_key_creds"] = auth_models.APIKeyCredentials(
username=username, api_key=api_key)
request_entity = auth_models.Auth(**kwargs)
r = client.request(
"POST", url, request_entity=request_entity,
response_entity_type=auth_models.AuthResponse)
if not r.ok:
raise Exception("Failed to authenticate")
r.entity = auth_models.AuthResponse.deserialize(
r.content, deserialize_format)
if r.entity is None:
raise Exception("Failed to parse Auth response Body")
return r
def get_access(section_name):
return TokensBehavior.get_access_data_config(
UserConfig(section_name=section_name), UserAuthConfig())

View File

@ -0,0 +1,52 @@
from cafe.engine.models.data_interfaces import ConfigSectionInterface
class UserAuthConfig(ConfigSectionInterface):
SECTION_NAME = 'auth'
@property
def endpoint(self):
return self.get("endpoint")
@property
def serialize_format(self):
return self.get("serialize_format", "json")
@property
def deserialize_format(self):
return self.get("deserialize_format", "json")
class UserConfig(ConfigSectionInterface):
SECTION_NAME = 'user'
@property
def username(self):
return self.get("username")
@property
def password(self):
return self.get_raw("password")
@property
def api_key(self):
return self.get_raw("api_key")
@property
def tenant_id(self):
return self.get("tenant_id")
@property
def tenant_name(self):
return self.get("tenant_name")
@property
def token(self):
return self.get("token")
@property
def endpoint(self):
"""Added to allow different users to auth at different endpoints. For
example the admin user needs to use an internal endpoint.
"""
return self.get("endpoint")

View File

@ -0,0 +1,452 @@
import json
from xml.etree import ElementTree as ET
from cafe.engine.models.base import (
AutoMarshallingModel, AutoMarshallingListModel)
class V2_0Constants(object):
XML_NS = 'http://docs.openstack.org/identity/api/v2.0'
XML_NS_OPENSTACK_COMMON = 'http://docs.openstack.org/common/api/v1.0'
XML_NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
XML_NS_OS_KSADM = \
'http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0'
XML_NS_OS_KSEC2 = \
'http://docs.openstack.org/identity/api/ext/OS-KSEC2/v1.0'
XML_NS_RAX_KSQA = \
'http://docs.rackspace.com/identity/api/ext/RAX-KSQA/v1.0'
XML_NS_RAX_KSKEY = \
'http://docs.rackspace.com/identity/api/ext/RAX-KSKEY/v1.0'
XML_NS_RAX_AUTH = \
'http://docs.rackspace.com/identity/api/ext/RAX-AUTH/v1.0'
XML_NS_RAX_KSGRP = \
'http://docs.rackspace.com/identity/api/ext/RAX-KSGRP/v1.0'
XML_NS_ATOM = 'http://www.w3.org/2005/Atom'
class BaseIdentityModel(AutoMarshallingModel):
_namespaces = V2_0Constants
def __init__(self, kwargs):
super(BaseIdentityModel, self).__init__()
for var in kwargs:
if var != "self" and not var.startswith("_"):
setattr(self, var, kwargs.get(var))
@classmethod
def _remove_xml_namespaces(cls, element):
for key, value in cls._namespaces.__dict__.items():
if key.startswith("__"):
continue
element = cls._remove_namespace(element, value)
return element
@classmethod
def _remove_namespace(cls, element, XML_NS):
return cls._remove_xml_etree_namespace(element, XML_NS)
@classmethod
def _json_to_obj(cls, serialized_str):
data_dict = json.loads(serialized_str)
return cls._dict_to_obj(data_dict)
@classmethod
def _xml_to_obj(cls, serialized_str):
element = ET.fromstring(serialized_str)
return cls._xml_ele_to_obj(
cls._remove_xml_namespaces(element))
def _obj_to_json(self):
return json.dumps(self._obj_to_dict())
def _obj_to_xml(self):
element = self._obj_to_xml_ele()
return ET.tostring(element)
@staticmethod
def _find(element, tag):
if element is None:
return ET.Element(None)
new_element = element.find(tag)
if new_element is None:
return ET.Element(None)
return new_element
class BaseIdentityListModel(AutoMarshallingListModel, BaseIdentityModel):
pass
class EmptyModel(object):
def _obj_to_dict(self):
return None
def _obj_to_xml_ele(self):
return ET.Element(None)
class AuthResponse(BaseIdentityModel):
def __init__(
self, token=None, service_catalog=None, user=None):
super(AuthResponse, self).__init__(locals())
@classmethod
def _dict_to_obj(cls, data):
return cls(
token=AuthResponseToken._dict_to_obj(data.get("token")),
user=User._dict_to_obj(data.get("user")),
service_catalog=ServiceCatalog._dict_to_obj(
data.get("serviceCatalog")))
@classmethod
def _json_to_obj(cls, serialized_str):
data = json.loads(serialized_str)
return cls._dict_to_obj(data.get("access"))
@classmethod
def _xml_ele_to_obj(cls, element):
service_catalog = ServiceCatalog._xml_ele_to_obj(
cls._find(element, "serviceCatalog"))
return cls(
token=AuthResponseToken._xml_ele_to_obj(
cls._find(element, "token")),
user=User._xml_ele_to_obj(cls._find(element, "user")),
service_catalog=service_catalog)
def get_service(self, name):
for service in self.service_catalog:
if service.name == name:
return service
return None
class Tenant(BaseIdentityModel):
def __init__(self, name=None, id_=None):
super(Tenant, self).__init__(locals())
@classmethod
def _xml_ele_to_obj(cls, element):
if element is None:
return cls()
return cls(
name=element.attrib.get("name"), id_=element.attrib.get("id"))
@classmethod
def _dict_to_obj(cls, data):
if data is None:
return cls()
return cls(
name=data.get("name"), id_=data.get("id"))
@classmethod
def _json_to_obj(cls, serialized_str):
data = json.loads(serialized_str)
return cls._dict_to_obj(data.get("tenant"))
class AuthResponseToken(BaseIdentityModel):
def __init__(
self, id_=None, expires=None, tenant=None, authenticated_by=None):
super(AuthResponseToken, self).__init__(locals())
@classmethod
def _dict_to_obj(cls, data):
return cls(id_=data.get("id"),
expires=data.get("expires"),
tenant=Tenant._dict_to_obj(data.get("tenant")),
authenticated_by=data.get("RAX-AUTH:authenticatedBy"))
@classmethod
def _xml_ele_to_obj(cls, element):
authenticated_by = cls._find(element, "authenticatedBy")
authenticated_by = authenticated_by.findtext("credential")
return cls(
id_=element.attrib.get("id"),
expires=element.attrib.get("expires"),
tenant=Tenant._xml_ele_to_obj(cls._find(element, "tenant")),
authenticated_by=authenticated_by)
class ServiceCatalog(BaseIdentityListModel):
@classmethod
def _dict_to_obj(cls, data):
return ServiceCatalog(
[Service._dict_to_obj(service) for service in data])
@classmethod
def _xml_ele_to_obj(cls, element):
return ServiceCatalog(
[Service._xml_ele_to_obj(service) for service in element])
class User(BaseIdentityModel):
def __init__(
self, id_=None, name=None, roles=None,
rax_auth_default_region=None, rax_auth_federated=None):
super(User, self).__init__(locals())
@classmethod
def _dict_to_obj(cls, data):
if data is None:
return cls()
region = data.get("RAX-AUTH:defaultRegion")
if isinstance(region, list):
region = region[0]
return cls(
id_=data.get("id"),
name=data.get("name"),
roles=Roles._dict_to_obj(data.get("roles")),
rax_auth_default_region=region,
rax_auth_federated=data.get("RAX-AUTH:federated"))
@classmethod
def _xml_ele_to_obj(cls, element):
return cls(
id_=element.attrib.get("id"),
name=element.attrib.get("name"),
roles=Roles._xml_ele_to_obj(cls._find(element, "roles")),
rax_auth_default_region=element.attrib.get("defaultRegion"),
rax_auth_federated=element.attrib.get("federated"))
class Service(BaseIdentityModel):
def __init__(self, endpoints=None, name=None, type_=None):
super(Service, self).__init__(locals())
def get_endpoint(self, region):
"""
Returns the endpoint that matches the provided region,
or None if such an endpoint is not found
"""
for endpoint in self.endpoints:
if getattr(endpoint, "region"):
if str(endpoint.region).lower() == str(region).lower():
return endpoint
return None
@classmethod
def _dict_to_obj(cls, data):
return cls(
endpoints=Endpoints._dict_to_obj(data.get("endpoints")),
name=data.get("name"), type_=data.get("type"))
@classmethod
def _xml_ele_to_obj(cls, element):
return cls(
endpoints=Endpoints._xml_ele_to_obj(
element.findall("endpoint")),
name=element.attrib.get("name"),
type_=element.attrib.get("type"))
class Endpoints(BaseIdentityListModel):
@classmethod
def _xml_ele_to_obj(cls, elements):
if not elements:
return cls()
return cls([Endpoint._xml_ele_to_obj(endp) for endp in elements])
@classmethod
def _dict_to_obj(cls, data):
if not data:
return cls()
return cls([Endpoint._dict_to_obj(endpoint) for endpoint in data])
@classmethod
def _json_to_obj(cls, string):
data = json.loads(string)
return cls._dict_to_obj(data.get("endpoints"))
class Endpoint(BaseIdentityModel):
def __init__(self, id_=None, tenant_id=None, region=None, type_=None,
public_url=None, internal_url=None, admin_url=None,
version_id=None, version_info=None, version_list=None,
name=None):
super(Endpoint, self).__init__(locals())
@classmethod
def _xml_ele_to_obj(cls, element):
version = element.find("version")
version_attrib = version.attrib if version is not None else {}
return cls(
id_=element.attrib.get("id"),
tenant_id=element.attrib.get("tenantId"),
region=element.attrib.get("region"),
type_=element.attrib.get("type"),
name=element.attrib.get("name"),
public_url=element.attrib.get("publicURL"),
internal_url=element.attrib.get("internalURL"),
admin_url=element.attrib.get("adminURL"),
version_id=version_attrib.get("id"),
version_info=version_attrib.get("info"),
version_list=version_attrib.get("list"))
@classmethod
def _dict_to_obj(cls, data):
return cls(
id_=data.get("id"),
tenant_id=data.get("tenantId"),
region=data.get("region"),
type_=data.get("type"),
name=data.get("name"),
public_url=data.get("publicURL"),
internal_url=data.get("internalURL"),
admin_url=data.get("adminURL"),
version_id=data.get("versionId"),
version_info=data.get("versionInfo"),
version_list=data.get("versionList"))
class Roles(BaseIdentityListModel):
@classmethod
def _xml_ele_to_obj(cls, elements):
return Roles(
[Role._xml_ele_to_obj(element) for element in elements])
@classmethod
def _dict_to_obj(cls, data):
return Roles([Role._dict_to_obj(obj) for obj in data])
class Role(BaseIdentityModel):
def __init__(
self, id_=None, name=None, description=None,
rax_auth_propagate=None, tenant_id=None, service_id=None):
super(Role, self).__init__(locals())
@classmethod
def _xml_ele_to_obj(cls, element):
if element is None:
return None
return cls(
id_=element.attrib.get("id"), name=element.attrib.get("name"),
description=element.attrib.get("description"),
rax_auth_propagate=element.attrib.get("propagate"),
service_id=element.attrib.get("serviceId"),
tenant_id=element.attrib.get("tenantId"))
@classmethod
def _dict_to_obj(cls, data):
if data is None:
return None
return cls(
id_=data.get("id"), name=data.get("name"),
description=data.get("description"),
rax_auth_propagate=data.get("rax-auth:propagate"),
service_id=data.get("serviceId"),
tenant_id=data.get("tenantId"))
class Auth(BaseIdentityModel):
xmlns = V2_0Constants.XML_NS
def __init__(
self, password_creds=None, rsa_creds=None, token_creds=None,
api_key_creds=None, domain=None, tenant_name=None, tenant_id=None):
password_creds = password_creds or EmptyModel()
token_creds = token_creds or EmptyModel()
api_key_creds = api_key_creds or EmptyModel()
super(Auth, self).__init__(locals())
def _obj_to_dict(self):
attrs = {
"tenantName": self.tenant_name,
"tenantId": self.tenant_id,
"passwordCredentials": self.password_creds._obj_to_dict(),
"RAX-KSKEY:apiKeyCredentials": self.api_key_creds._obj_to_dict(),
"token": self.token_creds._obj_to_dict()}
return {'auth': self._remove_empty_values(attrs)}
def _obj_to_xml_ele(self):
element = ET.Element('auth')
element = self._set_xml_etree_element(
element, {"tenantName": self.tenant_name, "xmlns": self.xmlns,
"tenantId": self.tenant_id})
element.append(self.password_creds._obj_to_xml_ele())
element.append(self.rsa_creds._obj_to_xml_ele())
element.append(self.token_creds._obj_to_xml_ele())
element.append(self.api_key_creds._obj_to_xml_ele())
element.append(self.domain._obj_to_xml_ele())
return element
class PasswordCredentials(BaseIdentityModel):
def __init__(self, username=None, password=None):
super(PasswordCredentials, self).__init__(locals())
def _obj_to_dict(self):
attrs = {"username": self.username, "password": self.password}
return self._remove_empty_values(attrs)
def _obj_to_xml_ele(self):
element = ET.Element('passwordCredentials')
attrs = {"username": self.username, "password": self.password}
return self._set_xml_etree_element(element, attrs)
class Token(BaseIdentityModel):
def __init__(self, id_=None):
super(Token, self).__init__(locals())
def _obj_to_dict(self):
attrs = {"id": self.id_}
return self._remove_empty_values(attrs)
def _obj_to_xml_ele(self):
element = ET.Element('token')
attrs = {"id": self.id_}
return self._set_xml_etree_element(element, attrs)
class APIKeyCredentials(BaseIdentityModel):
def __init__(self, username=None, api_key=None):
xmlns = V2_0Constants.XML_NS_RAX_KSKEY
super(APIKeyCredentials, self).__init__(locals())
def _obj_to_dict(self):
attrs = {"username": self.username, "apiKey": self.api_key}
return self._remove_empty_values(attrs)
def _obj_to_xml_ele(self):
element = ET.Element('apiKeyCredentials')
attrs = {
"username": self.username, "apiKey": self.api_key,
"xmlns": self.xmlns}
return self._set_xml_etree_element(element, attrs)
class RSACredentials(BaseIdentityModel):
xmlns = V2_0Constants.XML_NS_RAX_AUTH
def __init__(self, username=None, rsa_key=None):
super(RSACredentials, self).__init__(locals())
def _obj_to_dict(self):
attrs = {"username": self.username, "tokenKey": self.rsa_key}
return self._remove_empty_values(attrs)
def _obj_to_xml_ele(self):
element = ET.Element('RAX-AUTH:rsaCredentials')
attrs = {
"username": self.username, "tokenKey": self.rsa_key,
"xmlns:RAX-AUTH": self.xmlns}
return self._set_xml_etree_element(element, attrs)
class Domain(BaseIdentityModel):
xmlns = V2_0Constants.XML_NS_RAX_AUTH
def __init__(self, name=None):
super(Domain, self).__init__(locals())
def _obj_to_dict(self):
attrs = {"name": self.name}
return self._remove_empty_values(attrs)
def _obj_to_xml_ele(self):
element = ET.Element("RAX-AUTH:domain")
attrs = {"name": self.name, "xmlns:RAX-AUTH": self.xmlns}
return self._set_xml_etree_element(element, attrs)

View File

@ -0,0 +1,45 @@
import urlparse
from cafe.engine.behaviors import BaseBehavior
class RequestObject(object):
def __init__(
self, method, url, headers=None, params=None, data=None):
self.method = method
self.url = url
self.headers = headers or {}
self.params = params or {}
self.data = data or ""
class RequestCreator(BaseBehavior):
@staticmethod
def _parse(string, endpoint):
params = None
lines = string.splitlines()
method, url, http_version = lines[0].split()
url = url.split("?", 1)
if len(url) > 1:
params = {}
for param in url[1].split("&"):
param = param.split("=", 1)
if len(param) > 1:
params[param[0]] = param[1]
else:
params[param[0]] = ""
url = url[0]
url = urlparse.urljoin(endpoint, url)
for index, line in enumerate(lines):
if line == "":
break
headers = {}
for line in lines[1:index]:
key, value = line.split(":", 1)
headers[key] = value.strip()
data = "\n".join(lines[index+1:])
return RequestObject(method, url, headers, params, data)
@classmethod
def create_request():
pass

141
syntribos/runner.py Normal file
View File

@ -0,0 +1,141 @@
from __future__ import print_function
import argparse
import os
import pkgutil
import requests
import sys
from cafe.common.reporting.cclogging import init_root_log_handler
from cafe.configurator.managers import TestEnvManager
from cafe.drivers.unittest.arguments import ConfigAction
from cafe.drivers.base import print_exception
from syntribos import tests as package
#from syntribos.request_parser import RequestParser
class InputType(object):
def __init__(self, mode, bufsize):
self._mode = mode
self._bufsize = bufsize
def __call__(self, string):
if string == '-':
fp = sys.stdin
yield fp.name, fp.read()
elif os.path.isdir(string):
for path, _, files in os.walk(string):
for file_ in files:
file_path = os.path.join(path, file_)
fp = open(file_path, self._mode, self._bufsize)
yield file_path, fp.read()
fp.close()
elif os.path.isfile(string):
try:
fp = open(string, self._mode, self._bufsize)
yield fp.name, fp.read()
fp.close()
except Exception as e:
message = "can't open {}:{}"
raise Exception(message.format(string, e))
else:
message = "can't open {} not a readable file or dir"
raise Exception(message.format(string))
class SyntribosCLI(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super(SyntribosCLI, self).__init__(*args, **kwargs)
self._add_args()
def _add_args(self):
self.add_argument(
"config", metavar="<config>", action=ConfigAction,
help="test config. Looks in the ~/.opencafe/configs directory."
"Example: compute/dev.environ")
self.add_argument(
"input", metavar="<input_file>", type=InputType('rb', 0),
help="<input file|directory of files|-(for stdin)>")
self.add_argument(
"-t", "--test_types", metavar="TEST_TYPES", nargs="*", default=[],
help="Test types to run against api")
class Runner(object):
@classmethod
def populate_tests(cls):
if not os.environ.get("CAFE_CONFIG_FILE_PATH"):
os.environ["CAFE_CONFIG_FILE_PATH"] = "/dev/null"
for importer, modname, ispkg in pkgutil.walk_packages(
path=package.__path__,
prefix=package.__name__ + '.',
onerror=lambda x: None):
__import__(modname, fromlist=[])
@staticmethod
def print_symbol():
""" Syntribos radiation symbol """
border = '-' * 40
symbol = """
Syntribos
xxxxxxx
x xxxxxxxxxxxxx x
x xxxxxxxxxxx x
xxxxxxxxx
x xxxxxxx x
xxxxx
x xxx x
x
xxxxxxxxxxxxxxx xxxxxxxxxxxxxxx
xxxxxxxxxxxxx xxxxxxxxxxxxx
xxxxxxxxxxx xxxxxxxxxxx
xxxxxxxxx xxxxxxxxx
xxxxxx xxxxxx
xxx xxx
x x
x
=== Automated API Scanning ==="""
print(border)
print(symbol)
print(border)
@staticmethod
def print_log():
test_log = os.environ.get("CAFE_TEST_LOG_PATH")
if test_log:
print("=" * 150)
print("LOG PATH..........: {0}".format(test_log))
print("=" * 150)
@classmethod
def run(cls):
requests.packages.urllib3.disable_warnings()
try:
cls.print_symbol()
usage = """
syntribos <config> <input_file> --test-types=TEST_TYPES
syntribos <config> <input_file> -t TEST_TYPE TEST_TYPE ...
syntribos <config> <input_file>
"""
args, unknown = SyntribosCLI(usage=usage).parse_known_args()
test_env_manager = TestEnvManager(
"", args.config, test_repo_package_name="os")
test_env_manager.finalize()
cls.print_log()
init_root_log_handler()
cls.populate_tests()
except Exception as e:
print_exception(
file_="runner.py", method="entry_point", exception=e)
exit(1)
def entry_point():
return Runner.run()
if __name__ == '__main__':
entry_point()

View File

78
syntribos/tests/base.py Normal file
View File

@ -0,0 +1,78 @@
import os
from cafe.engine.models.data_interfaces import ConfigSectionInterface
from cafe.engine.http.client import HTTPClient
data_dir = os.environ.get("CAFE_DATA_DIR_PATH")
test_table = {}
class TestType(type):
def __new__(cls, cls_name, cls_parents, cls_attr):
new_class = super(TestType, cls).__new__(
cls, cls_name, cls_parents, cls_attr)
test_name = getattr(new_class, "test_name", None)
if test_name is not None:
if test_name in test_table:
msg = "Test name already used {}".format(test_name)
raise Exception(msg)
test_table[test_name] = new_class
return new_class
class BaseConfig(ConfigSectionInterface):
SECTION_NAME = None
@property
def percent(self):
return self.get("percent")
class BaseTest(object):
"""
Base for building new tests
"""
__metaclass__ = TestType
test_name = None
test_type = None
filename = None
config = None
@classmethod
def validate_test(cls, response):
return all([
response.status_code < 500,
cls.validate_length(response, cls.config.percent)])
@classmethod
def get_strings(cls):
path = os.path.join(data_dir, cls.filename)
return open(path, "rb")
@classmethod
def validate_length(cls, response, percent=5):
if getattr(cls, "init_response", False) is False:
raise NotImplemented
resp_len = len(response.content)
req_len = len(response.request.data)
iresp_len = len(cls.init_response.content)
ireq_len = len(cls.init_response.request.data)
request_diff = req_len - ireq_len
response_diff = resp_len - iresp_len
if request_diff == response_diff:
return True
elif resp_len == iresp_len:
return True
elif percent:
if abs(float(response_diff) / iresp_len) <= (percent / 100.0):
return True
return False
@classmethod
def run_test(cls, request, init_response):
client = HTTPClient()
r = client.request(
method=request.method, url=request.url, headers=request.headers,
params=request.params, data=request.data)
return cls.validate_test(r, init_response)

View File

@ -0,0 +1,23 @@
from syntribos.tests.base import BaseTest, BaseConfig
class SQLInjectionBody(BaseTest):
test_name = "SQL_INJECTION_BODY"
test_type = "BODY"
filename = "sql-injection.txt"
config = BaseConfig(section_name=test_name)
class SQLInjectionParams(SQLInjectionBody):
test_name = "SQL_INJECTION_PARAMS"
test_type = "PARAMS"
class SQLInjectionHeaders(SQLInjectionBody):
test_name = "SQL_INJECTION_HEADERS"
test_type = "HEADERS"
class SQLInjectionURL(SQLInjectionBody):
test_name = "SQL_INJECTION_URL"
test_type = "URL"