This repo contains a digitized version of the course content for CYBR8470 Secure Web App Development at the University of Nebraska at Omaha.
In this module, you will learn how to build a server of your own and connect it up to IFTTT (a web automation toolkit mostly used for the Internet of Things).
By the end of this tutorial, you will be able to:
Django server into a containerREST endpoint on the application serverFor this lesson, you will need:
We saw how containers could be used to host isolated servers on another host machine.
Now, in this lesson, we will examine how to create our own server and deploy it in a container.
For reference, this is the overall design we are looking at. On the left side, you have some sensors - they could be anything, since this isn’t an IoT class, we can just assume we are getting data from them. In this lesson, we will begin building the item marked custom web API in the figure below. It will have features to support authentication, logging events, and we can even include a hook to trigger another service (like IFTTT).

The process of creating a new application server from the ground up takes some time and attention. Instead of having you start from the ground up, I’ll provide you with some starter skeleton code. This code does the basics - you will extend it to accepting requests, store the data that comes in, and then trigger another service.
fork the repo for this lab by visiting the https://github.com/MLHale/cybr8470-web-service-lab and clicking ‘fork’. This will copy the code from our repository into your GitHub account - so you can track your changes as you go.git to clone the skeleton code repository and get it in onto our local machine.Open a new terminal instance:
cd Desktop
git clone https://github.com/<your-github-id without the brackets>/cybr8470-web-service-lab
cd cybr8470-web-service-lab/
Now use docker to build the image that our container will use, from the cybr8470-web-service-lab directory:
docker compose build
With this, we should be able to type the following and see our new image.
docker images
It will be called something like cybr8470-web-service-lab-backend_django.
This server is completely new, so we need to do some setup to get it initially configured. Execute the following to run the server and open up a bash terminal to interact with it.
docker compose up
This will initialize the server and tell our Django server to setup the database. It will create a database Schema that our SQL server can use to store data.
in a separate terminal:
docker compose exec django bash
In this terminal that opens in the container, we will use the manage utility to create new superuser account. Specify a password for admin. In development, you can use something simple (e.g. admin1234) for simplicity. In practice, you would want to use a much more secure password - since the server could be accessed from the wider internet.
python manage.py createsuperuser --username admin --email admin
exit
cybr8470-web-service-lab folderAlthough our server is already running, any time you want to stop it you can press Control + C. To bring it back up just type:
docker compose up
This server, diagrammatically looks like:

The docker command executes the container using the docker-compose.yml file located in your /cybr8470-web-service-lab/ folder.
port 80 on the host to port 8000 in the container.postgres database server.Dockerfile in your /cybr8470-web-service-lab/ folder to learn more about what happens behind the scenes.With the server running, you should be able to visit http://localhost to see your server.
This is a web client (also called a frontend) that I’ve built for demo purposes to work with our server. You will be making the server work with the client. I’ve included the client code in the /frontend folder, but you won’t need to modify it for this lab. Later labs will deal with client-side development.
Since our focus is the backend - lets take a look at our server environment. This is built with Python 2.7 and is intentionally using an old version of Django (the reasons will become clear in the next lab). First, Lets explore the file tree.
backend in the file tree to explore the actual files our server is usingapi and django_backend to expand out the folders and see what we have.Model View Controller framework called Django.
Models (in models.py) are abstraction mechanisms that help you represent your data without worrying about the nitty gritty of database technologies.Controllers (in controllers.py) and Views are modularization mechanisms that separate user-facing code (like user interfaces) from backend code that handles number crunching and getting data to the views.models.py, then urls.py, then controllers.pymodels.py you will see that we have defined two models: Event and ApiKey. Both of these are schema that have fields in them for each of the types of data they hold.ApiKey model we have fields for the owner and the actual key. These will hold our IFTTT key that we will use later to communicate with IFTTT via webhooks.Admin class that lists out the fields. This is used by Django’s admin interface to display the data for management.class ApiKey(models.Model):
owner = models.CharField(max_length=1000, blank=False)
key = models.CharField(max_length=5000, blank=False)
def __str__(self):
return str(self.owner) + str(self.key)
class ApiKeyAdmin(admin.ModelAdmin):
list_display = ('owner','key')
Event model we have fields for:
eventtype which describes what occurred,timestamp (when it happened)userid which is the id of the userrequestor which logs the IP of the client that sent the messageclass Event(models.Model):
eventtype = models.CharField(max_length=1000, blank=False)
timestamp = models.DateTimeField()
userid = models.CharField(max_length=1000, blank=True)
requestor = models.GenericIPAddressField(blank=False)
def __str__(self):
return str(self.eventtype)
class EventAdmin(admin.ModelAdmin):
list_display = ('eventtype', 'timestamp')
__str__ function which outputs a string if the model is converted to a string.Next lets look at urls.py. This file tells Django which URLs are accessible on the server. If a URL entry isn’t included in a urls.py file, then the method cannot be accessed.
url patterns that are acceptable for Django to server up to any would-be requestorscontrollers.py file. Basically, when someone attempts to visit a URL, Django goes through its list of acceptable patterns. If it matches a pattern it executes the corresponding code in that method. If it doesn’t match any acceptable pattern, it gives the user an HTTP 404 error (not found).api/urls.py is a sub set of patterns that are mapped behind /api/ as given in the file django_backend/urls.py.api/urls.py
urlpatterns = [
url(r'^session', csrf_exempt(controllers.Session.as_view())),
url(r'^register', csrf_exempt(controllers.Register.as_view())),
url(r'^', include(router.urls)),
]
django_backend/urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api/', include(api_urls)),
url(r'^xss-example/', controllers.xss_example),
url(r'^', controllers.home),
]
Next, lets look at the controllers.py file to see what the server does when a URL is visited.
There is some code in this file that handles session-based authentication and registration, but we need to create more code to make this app work.
Specifically the client is trying to retrieve events, so it can display them accordingly in the app. We need an API endpoint that handles requests to the /api/events URL.
Inspect the network console to see the requests the client is making.

Notice it is making a request to /api/events/ and getting a 405 Method not allow error - because our API does not support this endpoint.
class that extends the Django REST class APIView to implement our endpoint.APIView allows you to define functions that handle GET (single), GET(list), POST, PUT, and DELETE requests that might arrive at /api/eventsGET (single) request is used whenever a user wants to get a single item (typically by id), something like /api/events/4 would return the event with id 4.GET (list) request is used whenever a user wants to get all of the events.POST request is used whenever a user wants to make a new event.PUT request is used whenever a user wants to modify an existing event.DELETE request is used whenever a user wants to delete an existing event.These conventions are not specific to
Djangothey are based onRESTful APIdesign standards.
In our APIView we need to create two REST endpoints for handling POST requests and GET (list) requests.
The post function looks at the incoming request, extracts the data fields from it, and then creates and stores a new Event record based on the incoming request data. IF we were working with a real app, another sensor or service could call this endpoint and store data about an event that occured.
The get function simply queries the database for all Event objects and returns them to the requestor in JSON format
class Events(APIView):
permission_classes = (AllowAny,)
parser_classes = (parsers.JSONParser,parsers.FormParser)
renderer_classes = (renderers.JSONRenderer, )
def get(self, request, format=None):
events = Event.objects.all()
json_data = serializers.serialize('json', events)
content = {'events': json_data}
return HttpResponse(json_data, content_type='json')
def post(self, request, *args, **kwargs):
print 'REQUEST DATA'
print str(request.data)
eventtype = request.data.get('eventtype')
timestamp = int(request.data.get('timestamp'))
userid = request.data.get('userid')
requestor = request.META['REMOTE_ADDR']
newEvent = Event(
eventtype=eventtype,
timestamp=datetime.datetime.fromtimestamp(timestamp/1000, pytz.utc),
userid=userid,
requestor=requestor
)
try:
newEvent.clean_fields()
except ValidationError as e:
print e
return Response({'success':False, 'error':e}, status=status.HTTP_400_BAD_REQUEST)
newEvent.save()
print 'New Event Logged from: ' + requestor
return Response({'success': True}, status=status.HTTP_200_OK)
Django REST Framework each of these items is defined. They are unimportant, at the moment.GET method that we need to support our client.
Query using Django’s Database management system (DBMS) to get all of the events..e.g. Event.objects.all()events (which is what our client was expecting).GET request handler, we also have a POST handler.
BAD_REQUEST error response.api/urls.py and we can see that we already have a url for /events:new api/urls.py
urlpatterns = [
...
url(r'^events', csrf_exempt(controllers.Events.as_view())),
...
]
api/eventsPOSTMAN
docker compose up<myserver>/api/events a valid URL?<myserver>/api/session a valid URL?<myserver>/api/register?DELETE request to <myserver>/api/events?POST request to <myserver>/api/events?Ok, so you now have a loose familiarity with the skeleton backend code that was provided to you. Lets build some more upon it.
Go ahead and click login (on the left hand side menu). Authenticate with the username/password we made earlier (admin/admin1234).
When you login, you should see a green button that says turn IFTTT on when visiting localhost. Time to push it!
Lets use the chrome development tools to take a closer look.
Inspectchrome development tools which have a number of very helpful capabilities for debugging websites and inspecting the behind-the-scenes action that happens when you interact with web contentInstead of me reinventing the wheel, head over to https://developers.google.com/web/tools/chrome-devtools/ to learn the basics of what Chrome Development tools can do.
When you’ve looked over the different features. Come back and click on the network tab to inspect what is happening with our button.
activateIFTTT you will see the exact request that is getting sent.method not allowed like we saw before. This is because we haven’t actually defined or enabled the activateIFTTT endpoint on the server-side yet. We will do that next.response tab you will see the raw response that the server is returning when this button is clicked.Currently, the server doesn’t know that it needs to do anything special with the URL /api/activateIFTTT so it is just rendering the home page (what we have been looking at this whole time) in response. What we need is for our server to recognize that a new event has occurred from the client and then do something to handle it, in this case, contact IFTTT.
For this to work, we need to create a new REST Endpoint controller to handle the request. Open up your controllers.py file and add a new entry called ActivateIFTTT. This entry will only expose a POST endpoint. The goal is to:
test and log the resulting event locallyclass ActivateIFTTT(APIView):
permission_classes = (AllowAny,)
parser_classes = (parsers.JSONParser,parsers.FormParser)
renderer_classes = (renderers.JSONRenderer, )
def post(self,request):
print 'REQUEST DATA'
print str(request.data)
eventtype = request.data.get('eventtype')
timestamp = int(request.data.get('timestamp'))
requestor = request.META['REMOTE_ADDR']
api_key = ApiKey.objects.all().first()
event_hook = "test"
print "Creating New event"
newEvent = Event(
eventtype=eventtype,
timestamp=datetime.datetime.fromtimestamp(timestamp/1000, pytz.utc),
userid=str(api_key.owner),
requestor=requestor
)
print newEvent
print "Sending Device Event to IFTTT hook: " + str(event_hook)
#send the new event to IFTTT and print the result
event_req = requests.post('https://maker.ifttt.com/trigger/'+str(event_hook)+'/with/key/'+api_key.key, data= {
'value1' : timestamp,
'value2': "\""+str(eventtype)+"\"",
'value3' : "\""+str(requestor)+"\""
})
print event_req.text
#check that the event is safe to store in the databse
try:
newEvent.clean_fields()
except ValidationError as e:
print e
return Response({'success':False, 'error':e}, status=status.HTTP_400_BAD_REQUEST)
#log the event in the DB
newEvent.save()
print 'New Event Logged'
return Response({'success': True}, status=status.HTTP_200_OK)
Now that we have the endpoint defined we need to make it available on the web server. Modify api/urls.py to include a new line in the urlpatterns, make it look like:
urlpatterns = [
url(r'^session', csrf_exempt(controllers.Session.as_view())),
url(r'^register', csrf_exempt(controllers.Register.as_view())),
url(r'^events', csrf_exempt(controllers.Events.as_view())),
url(r'^activateifttt', csrf_exempt(controllers.ActivateIFTTT.as_view())),
url(r'^', include(router.urls)),
]
This will make the endpoint available on the webserver. Now go back to http://localhost and try to click the button. What happens?
Did you get an error? Did you restart your server? If not, press control+C on the docker terminal and then:
docker compose up
again.
Now try? Did you get a different error?
This is because we haven’t added our API Key to our server, so the field api_key = ApiKey.objects.all().first() returns null (or NoneType). To fix this, open your browser and go to http://localhost/admin/api/apikey/. Click ‘add api key’.
admin) in the owner field.key field add in your IFTTT API key.
https://maker.ifttt.com/use/test to receive our events.
IF condition select webhook, then select receive a web requestthen condition you can have IFTTT send you an email or any number of other events. Pick one. When done, save the applet.Now go back to your app at localhost and click the green button. What happened?
Since you made some changes to your code repository, lets track the changes with git:
git status
git add -A
git status
git commit -m "added endpoints for events and IFTTT"
git push
note if you have a git authentication error - it is probably because you need to handle your credentials correctly - i recommend github desktop or the git credential manager (https://github.com/git-ecosystem/git-credential-manager)
You just pushed your local changes to remote on github!
modules that exemplifies the modularization Cybersecurity First Principle. They don’t rely on the other modules (endpoints).API key in the code to protect it from static lookup - this is an example of the information hiding Cybersecurity First Principle.abstraction and resource encapsulation.web service!Pretty neat. Observe your handy work.
Lesson content: Copyright (C) Dr. Matthew Hale 2017-2023.

This lesson is licensed by the author under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.