Create your own REST testing application

This time i want show you how to create your own REST testing application. For this demonstration i use Python, Tkinter and some Python libraries. At the end of this tutorial you can extend the application with more features like “show headers”, “store requests/responses”, “run automatically” and so on.

Preparation

  • install Python > 2.7 (Tkinter included)
  • install Requests: HTTP for Humans
  • install Python Data Validation for Humans
requests==2.8.1
validators==0.9

Example

# -*- coding: utf-8 -*-
from Tkinter import Tk, FALSE


class BaseTkGui(object):
    """Define basic TK GUI"""

    def __init__(self, window_title, window_resizable):
        """Constructor for Tkinter GUI"""

        self._root = Tk()
        self._root.title(str(window_title))

        if not bool(window_resizable):
            self._root.resizable(width=FALSE, height=FALSE)
        else:
            self._root.columnconfigure(0, weight=1)
            self._root.rowconfigure(0, weight=1)

    def start_app(self):
        """Start TK loop"""

        self._root.mainloop()

    def quit_app(self):
        """Stop and quit application"""

        self._root.quit()
# -*- coding: utf-8 -*-
from Tkinter import (Frame, OptionMenu, Entry, Button, StringVar, Label,
                     W, E, NO, END, SOLID)
from ttk import Treeview, Separator
from ScrolledText import ScrolledText
from BaseGui import BaseTkGui


class ApplicationTkGui(BaseTkGui):
    """Define application TK GUI"""

    OPTIONS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS']

    def __init__(self, window_title):
        """Constructor for specific application GUI"""

        BaseTkGui.__init__(self, window_title, False)

        self._method = None
        self._url = None
        self._tree = None
        self._status = None
        self._time = None
        self._key = None
        self._value = None
        self._output = None

    def build_frames(self):
        """Add all frames and start mainloop"""

        self.__top_frame()
        self.__middle_frame()
        self.__bottom_frame()

        self.start_app()

    def prepare_req(self):
        """Overwritten method"""

        pass

    def _add_items(self):
        """Add items into TreeView at end"""

        key = str(self._key.get())
        value = str(self._value.get())

        if key and value:
            self._tree.insert("", "end", values=(key, value))
            self._key.delete(0, END)
            self._value.delete(0, END)

        self._tree.bind("<Double-1>", self._delete_item)

    def _delete_item(self, event):
        """Delete items from TreeView by ID"""

        item = self._tree.identify_row(event.y)
        self._tree.delete(item)

    def __top_frame(self):
        """Top frame creator"""

        self._method = StringVar(self._root)
        self._method.set("GET")

        frame = Frame(self._root)
        frame.grid(column=0, row=0, sticky=W+E)
        frame.grid_rowconfigure(0, weight=1)
        frame.grid_columnconfigure(0, weight=1)

        option = OptionMenu(frame, self._method, *self.OPTIONS)
        option.grid(column=0, row=0, padx=5, pady=5)

        self._url = Entry(frame, width=50)
        self._url.grid(column=1, row=0, padx=5, pady=5)
        self._url.configure(borderwidth=1, relief=SOLID)
        self._url.configure(highlightthickness=0)
        self._url.insert(0, 'http://')

        submit = Button(frame, text='Submit', command=self.prepare_req)
        submit.grid(column=3, row=0, padx=5, pady=5)

        Separator(frame).grid(columnspan=4, row=1, sticky=W+E)

    def __middle_frame(self):
        """Middle frame creator"""

        frame = Frame(self._root)
        frame.grid(column=0, row=1, sticky=W+E)
        frame.grid_rowconfigure(0, weight=1)
        frame.grid_columnconfigure(0, weight=1)

        self._tree = Treeview(frame, columns=("Key", "Val"), selectmode='none')
        self._tree.grid(columnspan=5, row=0, padx=5, pady=5, sticky=W+E)
        self._tree.column('#0', stretch=NO, minwidth=0, width=0)
        self._tree.heading('#1', text='Key')
        self._tree.heading('#2', text='Value')
        self._tree.configure(height=5)

        key = Label(frame, text='Key:')
        key.grid(column=0, row=1, padx=5, pady=5, sticky=E)
        self._key = Entry(frame)
        self._key.grid(column=1, row=1, padx=5, pady=5, sticky=W)
        self._key.configure(borderwidth=1, relief=SOLID)
        self._key.configure(highlightthickness=0)

        value = Label(frame, text='Value:')
        value.grid(column=2, row=1, padx=5, pady=5, sticky=E)
        self._value = Entry(frame)
        self._value.grid(column=3, row=1, padx=5, pady=5, sticky=W)
        self._value.configure(borderwidth=1, relief=SOLID)
        self._value.configure(highlightthickness=0)

        add = Button(frame, text='Add new header', command=self._add_items)
        add.grid(column=4, row=1, padx=5, pady=5)

        Separator(frame).grid(columnspan=5, row=2, sticky=W+E)

    def __bottom_frame(self):
        """Bottom frame creator"""
        font_style = ('verdana', 10, 'normal')

        frame = Frame(self._root)
        frame.grid(column=0, row=2, sticky=W+E)
        frame.grid_rowconfigure(0, weight=1)
        frame.grid_columnconfigure(0, weight=1)

        self._status = Label(frame, text='Response Status: -')
        self._status.grid(column=0, row=0, sticky=W)
        self._status.configure(fg='red', font=font_style)

        self._time = Label(frame, text='Time: - ms')
        self._time.grid(column=1, row=0, sticky=E)
        self._time.configure(fg='red', font=font_style)

        self._output = ScrolledText(frame)
        self._output.grid(columnspan=2, row=1, padx=5, pady=5, sticky=W+E)
        self._output.configure(height=12, borderwidth=1, relief=SOLID)
        self._output.configure(highlightthickness=0)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import validators
from Tkinter import END
from ApplicationGui import ApplicationTkGui


class Application(ApplicationTkGui):
    """Define application behaviors"""

    def __init__(self, window_title):
        """Constructor for application with title"""

        ApplicationTkGui.__init__(self, window_title)

    def run_application(self):
        """Start application"""

        self.build_frames()

    def prepare_req(self):
        """Prepare and validate request"""

        headers = dict()
        method = self._method.get()
        url = self._url.get()
        tree_ids = self._tree.get_children()

        for i in tree_ids:
            row = self._tree.item(i)
            add = row['values']
            headers[add[0]] = add[1]

        if validators.url(url):
            self.__do_request(method, url, headers)

    def __do_request(self, method, url, headers):
        """Do request and print results"""

        self._output.delete(1.0, END)

        ses = requests.Session()
        prepped = requests.Request(method, url, headers).prepare()
        response = ses.send(prepped, verify=False, allow_redirects=True)

        code = response.status_code
        body = response.text
        time_delta = response.elapsed
        duration = time_delta.total_seconds()

        status = 'Response Status: ' + str(code)
        time = 'Time: ' + str(duration) + ' ms'
        text = body[0:900] + ' ...'

        self._status.configure(text=status)
        self._time.configure(text=time)
        self._output.insert(END, text)

if __name__ == '__main__':

    RUN = Application('HTTPTester')
    RUN.run_application()

Run application

# set access permissions
$ chmod u+x ./RESTme.py
 
# start application
$ python -B ./RESTme.py

The application should look like:

RESTme Application

Create your own fake-data generator application

In this episode i want to show you, how easy it is to create a fake-data generator application.

Why?

  1. to get inspired
  2. if you need such tool behind restricted areas (without internet)
  3. software testers need a lot of fake-data

Required

  • Python > 2.7
  • Python Faker package installed
# install by pip
$ sudo pip install fake-factory

Example Application Code

# -*- coding: utf-8 -*-
from faker import Factory


class BasicGenerator(object):

    SETTINGS = ('Prefix', 'Name', 'Address', 'E-Mail', 'Phone',
                'Birthday', 'Company')

    def __init__(self):
        """Constructor for vars and factory object"""
        self._repeat_count = None
        self._selection = None
        self.__faker = Factory.create()

    def faker_data(self, count, selection):
        """Return fake data string"""
        output = str()
        loops = int(count)
        value = tuple(selection)
        for i in range(0, loops):
            if 0 in value:
                output += self.__faker.prefix()
            if 1 in value:
                output += self.__faker.name() + '\n'
            if 2 in value:
                output += self.__faker.address() + '\n'
            if 3 in value:
                output += self.__faker.email() + '\n'
            if 4 in value:
                output += self.__faker.phone_number() + '\n'
            if 5 in value:
                output += self.__faker.date() + '\n'
            if 6 in value:
                output += self.__faker.company() + '\n'
            output += '\n'
        return output
# -*- coding: utf-8 -*-
from BasicGenerator import BasicGenerator
from Tkinter import (Tk, Button, Variable, Listbox, MULTIPLE, FALSE, END,
                     Spinbox, E, W)
from ScrolledText import ScrolledText


class FakeNameGenerator(BasicGenerator):

    def __init__(self):
        """Constructor for Tkinter GUI"""
        BasicGenerator.__init__(self)
        self.__root = Tk()
        self.__root.title('FakeNameGenerator')
        self.__root.resizable(width=FALSE, height=FALSE)
        self.__result_output = None
        self.__box_opt = Variable(self.__root)
        self.__box_opt.set(self.SETTINGS)

    def start_app(self):
        """Build widgets and start TK loop"""
        self.__control_widget()
        self.__output_widget()
        self.__root.mainloop()

    def quit_app(self):
        """Stop and close application"""
        self.__root.quit()

    def __reset_output(self):
        """Reset TK text"""
        self.__result_output.delete(1.0, END)

    def __run_fake_data(self):
        """Validate and generate output"""
        count = int(self._repeat_count.get())
        selection = self._selection.curselection()
        if not selection:
            self.__result_output.insert(END, 'No value selected!\n\n')
        elif count < 1:
            self.__result_output.insert(END, 'Min. 1 need!\n\n')
        elif count > 10:
            self.__result_output.insert(END, 'Max. 10 allowed!\n\n')
        else:
            data = self.faker_data(count, selection)
            self.__result_output.insert(END, data)

    def __control_widget(self):
        """Build control widgets"""
        self._selection = Listbox(self.__root,
                                  listvariable=self.__box_opt,
                                  selectmode=MULTIPLE)
        self._selection.grid(padx=5, column=1, row=1, sticky=W+E)
        self._repeat_count = Spinbox(self.__root,
                                     from_=1,
                                     to=10)
        self._repeat_count.grid(column=1, row=2, sticky=W+E)
        Button(self.__root,
               text='Create',
               command=self.__run_fake_data).grid(column=1, row=3, sticky=W+E)
        Button(self.__root,
               text='Reset',
               command=self.__reset_output).grid(column=1, row=4, sticky=W+E)
        Button(self.__root,
               text='Quit',
               command=self.quit_app).grid(column=1, row=6, sticky=W+E)

    def __output_widget(self):
        """Build output widget"""
        self.__result_output = ScrolledText(self.__root, width=80)
        self.__result_output.grid(padx=5, pady=5, column=0, row=0, rowspan=7)
        self.__result_output.config(borderwidth=1, highlightthickness=1)


if __name__ == '__main__':
    RUN = FakeNameGenerator()
    RUN.start_app()

Run Fake-Data Application

# set access permissions
$ chmod u+x FakeNameGenerator.py

# start application
$ python -B ./FakeNameGenerator.py

Now it`s on you to improve this tiny application! As example you could add file and/or database export, add more functionality or just improve the GUI.

Create information gathering test application

It is time again for an extensive tutorial. This time, a tiny test application for passive and active information gathering. After the instruction you are welcome to improve the application with more features! Okay let’s start…

What should it do?

The security tester selects a information gathering method first. As second step the testers insert the URL or IP in a testfield and press a button. The result should printed out in a text area. The GUI should look like this:

Sensei Mockup

How it is implemented?

The prefered language is Python 2.7. So it is portable to different OS and for the most of methods are already packages available. The GUI is done with Tkinter. Tkinter provides all objects which are needed as widgets and ranges for this scope out completely. The file and folder structure look like:

├── essential
│   ├── __init__.py
│   └── timestop.py
├── gathering
│   ├── __init__.py
│   ├── geolocation.py
│   ├── icmpping.py
│   ├── information.py
│   ├── wappalyzer.py
│   └── whoisgathering.py
├── requirements.txt
└── sensei.py

File content

Files in root directory:

requests
python-Wappalyzer
python-whois
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from urlparse import urlparse
from gathering import IcmpPing, WhoisGathering, GeoLocation, WappAlyzer
from Tkinter import (Tk, Frame, StringVar, OptionMenu, Entry, Button, Text,
                     W, E, END, FALSE)


class Sensei(object):

    def __init__(self):
        self.root = Tk()
        self.root.title('Sensei')
        self.root.resizable(width=FALSE, height=FALSE)
        self.select = [
            'ICMP Ping', 'Whois', 'GeoLocation', 'Wappalyzer'
        ]
        self.option = StringVar(self.root)
        self.option.set(self.select[0])
        self.method = None
        self.url = None
        self.result = None

    def create_gui(self):
        self._top_frame()
        self._output_frame()
        self.root.mainloop()

    def quite_app(self):
        self.root.quit()

    def _start_request(self):
        self.result.delete(1.0, END)
        action = None
        target = self.url.get()
        method = self.option.get()
        if method == 'ICMP Ping':
            action = IcmpPing()
        elif method == 'Whois':
            action = WhoisGathering()
        elif method == 'GeoLocation':
            action = GeoLocation()
        elif method == 'Wappalyzer':
            action = WappAlyzer()
        if target:
            action.set_target(target)
            action.do_request()
            value = action.get_result()
        else:
            value = 'Internal Error'
        self.result.insert(END, value)

    def check_protocol(self, value):
        target = self.url.get()
        if value == 'Wappalyzer':
            if 'http' not in target:
                self.url.insert(0, 'http://')
        else:
            if 'http' in target:
                new_target = urlparse(target)
                self.url.delete(0, END)
                self.url.insert(0, new_target[1])

    def _top_frame(self):
        top_frame = Frame(self.root)
        top_frame.grid(column=0, row=0, sticky=W+E)
        self.method = OptionMenu(
            top_frame, self.option, *self.select, command=self.check_protocol
        )
        self.method.config(width=15)
        self.method.grid(column=0, row=0)
        self.url = Entry(top_frame, width=50)
        self.url.grid(column=1, row=0)
        Button(top_frame, text='Request', command=self._start_request).grid(
            column=2, row=0)
        Button(top_frame, text='Close', command=self.quite_app).grid(
            column=3, row=0)

    def _output_frame(self):
        output_frame = Frame(self.root)
        output_frame.grid(column=0, row=2, sticky=W+E)
        self.result = Text(output_frame, height=15)
        self.result.grid(column=0, row=0)


if __name__ == '__main__':
    RUN = Sensei()
    RUN.create_gui()

Files in essential:

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from essential.timestop import TimeStop
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from datetime import datetime


class TimeStop(object):
    """missing docstring"""

    __start = None

    @classmethod
    def start_measure(cls):
        cls.__start = datetime.now()

    @classmethod
    def stop_measure(cls):
        stop = datetime.now()
        total = stop - cls.__start
        return ">>>> request complete in " + str(total)

Files in gathering:

#!/usr/bin/env python
#  -*- coding: utf-8 -*-

from gathering.information import InformationGathering
from gathering.icmpping import IcmpPing
from gathering.whoisgathering import WhoisGathering
from gathering.geolocation import GeoLocation
from gathering.wappalyzer import WappAlyzer
#!/usr/bin/env python
#  -*- coding: utf-8 -*-


class InformationGathering(object):

    def __init__(self):
        self.errors = 0
        self.target = ''
        self.result = ''

    def set_target(self, target):
        victim = target.strip(' \t\n\r')
        if not victim:
            self.errors += 1
            self.result = 'No target given!'
        else:
            self.target = target

    def get_result(self):
        return self.result
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from gathering.information import InformationGathering
from essential.timestop import TimeStop
import os
import platform


class IcmpPing(InformationGathering):

    COMMAND = ''

    def __create_command(self):
        operation_system = platform.system()
        if operation_system == "Windows":
            self.COMMAND = "ping -n 1 "
        elif operation_system == "Linux":
            self.COMMAND = "ping -c 1 "
        else:
            self.COMMAND = "ping -c 1 "

    def do_request(self):
        if self.errors == 0:
            self.__create_command()
            command = self.COMMAND + self.target
            TimeStop.start_measure()
            response = os.popen(command)
            for line in response.readlines():
                self.result += line
            self.result += TimeStop.stop_measure()
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from gathering.information import InformationGathering
from essential.timestop import TimeStop
import whois


class WhoisGathering(InformationGathering):

    def do_request(self):
        if self.errors == 0:
            TimeStop.start_measure()
            result = whois.whois(self.target)
            self.result += result.text
            self.result += TimeStop.stop_measure()
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from gathering.information import InformationGathering
from essential.timestop import TimeStop
import requests


class GeoLocation(InformationGathering):

    API = 'http://ip-api.com/json/'

    def do_request(self):
        if self.errors == 0:
            target = self.API + self.target
            TimeStop.start_measure()
            response = requests.get(target)
            output = response.json()
            for key, val in output.items():
                if val:
                    self.result += str(key) + " => " + str(val) + "\n"
            self.result += TimeStop.stop_measure()
#!/usr/bin/env python
#  -*- coding: utf-8 -*-
from gathering.information import InformationGathering
from essential.timestop import TimeStop
from Wappalyzer import Wappalyzer, WebPage


class WappAlyzer(InformationGathering):

    def do_request(self):
        if self.errors == 0:
            TimeStop.start_measure()
            wappalyzer = Wappalyzer.latest()
            website = WebPage.new_from_url(self.target)
            output = wappalyzer.analyze(website)
            for val in output:
                self.result += " => " + str(val) + "\n"
            self.result += TimeStop.stop_measure()

That was it. The result looks like this:

Sensei

Improve it with your ideas!

Create your own test application

A lot of software testers do have no or less development skills. They also have less skills to use commandline tools and need GUI applications. In addition, the test applications for the respective claims should be easy to use. This guide will show you how to easily deploy software testers the needed test tools. This is just an example, please feel free to expand it!

Precondition

Let`s go

After create a new PyCharm project (with virtualenv), create 2 new empty files (requirements.txt, ShowHeaders.py) and import a icon. You can found icons (*.icns) on iconarchive for free.

application project files

Open the “requirements.txt” file and add Requests library.

requests==2.6.0

Open the “ShowHeader.py” and add the following content.

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
"""
This is a main script

Usage:
    python ShowHeaders.py
"""
import requests
from Tkinter import (Tk, Frame, StringVar, OptionMenu, Button, Entry, Text,
                     END, DISABLED, NORMAL, SUNKEN, E, W)


class ShowHeaders(object):
    """
    ShowHeaders class
    """

    OPTIONS = ["GET", "POST", "PUT", "DELETE"]

    def __init__(self):
        """
        Constructor for Tk GUI
        """
        self.root = Tk()
        self.root.title('Show Headers')
        self.root.configure(bg="light blue")
        self.option = StringVar(self.root)
        self.option.set(self.OPTIONS[0])
        self.methods = None
        self.url = None
        self.response = None
        self.copy = None

    def create_gui(self):
        """
        Create Tk GUI
        """
        self._create_top_frame()
        self._create_middle_frame()
        self._create_bottom_frame()
        self.root.mainloop()

    def close_app(self):
        """
        Close & Quit Application
        """
        self.root.quit()

    def _print_response(self, response_txt):
        """
        Print response
        """
        self.response.config(state=NORMAL)
        self.response.delete(1.0, END)
        self.response.insert(END, response_txt)
        self.response.config(state=DISABLED)

    def _copy_to_clipboard(self):
        """
        Copy to text clipboard
        """
        text = self.response.get("1.0", END)
        self.root.clipboard_clear()
        self.root.clipboard_append(text)

    def _make_request(self):
        """
        Run http request and print
        """
        self.copy.config(state="normal")
        methods = self.option.get()
        url = self.url.get()
        if methods == 'GET':
            req = requests.get(url)
        elif methods == 'POST':
            req = requests.post(url)
        elif methods == 'PUT':
            req = requests.put(url)
        elif methods == 'DELETE':
            req = requests.delete(url)
        else:
            req = dict()
        header = req.headers
        self._print_response(header)

    def _create_top_frame(self):
        """
        Create top frame
        """
        top_frame = Frame(self.root)
        top_frame.grid(row=0, column=0, padx=5, pady=5, sticky=W+E)

        self.methods = OptionMenu(top_frame, self.option, *self.OPTIONS)
        self.methods.config(width=15)
        self.methods.grid(row=0, column=0)

        self.url = Entry(top_frame, width=50)
        self.url.insert(0, "http://")
        self.url.grid(row=0, column=1)

        Button(top_frame, text='Request', command=self._make_request).grid(
            row=0, column=2)

    def _create_middle_frame(self):
        """
        Create middle frame
        """
        middle_frame = Frame(self.root, height=75, bd=1, relief=SUNKEN)
        middle_frame.grid(row=1, column=0, padx=5, pady=5)

        self.response = Text(middle_frame, height=10)
        self.response.config(state=DISABLED)
        self.response.grid(row=1)

    def _create_bottom_frame(self):
        """
        Create bottom frame
        """
        bottom_frame = Frame(self.root)
        bottom_frame.grid(row=2, column=0, padx=5, pady=5, sticky=W+E)

        self.copy = Button(bottom_frame, text="Copy", state=DISABLED,
                           command=self._copy_to_clipboard)
        self.copy.grid(row=0, column=0)

        Button(bottom_frame, text='Quit', command=self.close_app).grid(
            row=0, column=1)


if __name__ == '__main__':
    APP = ShowHeaders()
    APP.create_gui()

For first test, run the application. If there are no issues – open the PyCharm terminal and run the following command.

$ py2applet --make-setup ShowHeaders.py

# or if you use virtualenv
$ /Users/<username>/ShowHeaderEnv/bin/py2applet --make-setup ShowHeaders.py

Now you should see the generated file “setup.py” in the Project. Open the file and add the icon. The content should look like this:

"""
This is a setup.py script generated by py2applet

Usage:
    python setup.py py2app
"""

from setuptools import setup

APP = ['ShowHeaders.py']
DATA_FILES = []
OPTIONS = {'argv_emulation': True, 'iconfile':'header.icns'}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

Now execute the following command to build, for first test.

$ python setup.py py2app -A

After some messages in terminal, your Project should show 2 new folders (build, dist). The alias mode (-A / –alias) instructs py2app to build an application, but it is not portable to other machines! You can open the Application in Finder or from terminal for your tests. If there are changes, just run the build-command again.

$ open dist/ShowHeaders.app

If everything is fine and your Application is ready for ship, run the following command.

# remove build and dist folder
$ rm -fr build/ dist/

# build application
$ python setup.py py2app

Again the Application can be found on folder “dist”. The result should look like this:

example application

Have fun and be creative! Too ship the test application for Windows users, look at py2exe.