data编程代写、代做C++,Java程序
- 首页 >> Matlab编程 DISTRIBUTED SYSTEMS API
Distributed Systems ACW
2023/24
You have been hired as a distributed systems programmer after Gribbald, their previous expert disappeared one foggy
night. You’ve been promised that your first job for them won’t be very hard and, thankfully, it’s really not. The task is
to develop a client/server, using ASP.NET Core Web API, which provides a number of extra secret security and
encryption services… sort of.
You’re lucky though. Gribbald was working on this project before you and has already created an outline solution with
some ‘TODO’ regions, based on the original specification. They have also broken down the specification into tasks for
you, which should make your life even easier!
On your first day you were handed this specification:
• The server must be able to handle multiple client requests simultaneously.
• The server must use Entity Framework, Code First, to create and manage a local database of Users (that will,
in the future be moved over to a production database).
• The client must be able to get a TalkBack/Hello message from the server. The server will respond with “Hello
World”.
• The client must be able to send a TalkBack/Sort message as a get request to the server with an array of
integers as parameters. The server will sort the integers into ascending order and return the sorted array to
the client, because – well, sorting data is tedious.
• The client must be able to send a User/New get request to the server which has a username string as a
parameter. The server must return a string identifying if the username already exists in the database.
• The client must be able to send a User/New post request to the server which has a username string in the
request body. The server must create a new user, generate a new GUID as an API Key, save the user to the
database and return the API Key to the user. If this is the first user they should be saved as Admin role,
otherwise just with User role.
• The client must be able to send a User/RemoveUser delete request to the server with an API Key in the header
and a string username in the URI. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must check that the username and API Key are the same user and if they are, it
must delete this user from the database.
• The client must be able to send a User/ChangeRole Post request to the server with an API Key in the header
which links to an Admin role user, a string username in the body and a string role in the body. If the server
receives this request, it must check to see if the API Key is in the database and whether the role is Admin, if it
is, it must update the role for the given username to the role provided (User or Admin)
• The client must be able to send a Protected/Hello get request to the server with an API Key in the header. If
the server receives this request, it must check to see if the API Key is in the database and, if it is, it must get
the username associated with the API Key and send back “Hello” (e.g. “Hello UserOne”).
• The client must be able to send a Protected/SHA1 get request to the server with an API Key in the header and
a string message as a parameter. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must compute the SHA1 hash of the message and return it in hexadecimal form to
the client.
• Somebody told the boss that SHA256 is more secure than SHA1 so the client must be able to send a
Protected/SHA256 get request to the server with an API Key in the header and a string message as a
parameter. If the server receives this request, it must check to see if the API Key is in the database and, if it
is, it must compute the SHA256 hash of the message and return it in hexadecimal form to the client.
2
• The client must be able to send a Protected/GetPublicKey get request to the server with an API Key in the
header. If the server receives this request, it must check to see if the API Key is in the database and, if it is, it
must send back its RSA public key.
• The client must be able to send a Protected/Sign request with an API Key in the header and a string message
as a parameter. If the server receives this request, it must check to see if the API Key is in the database and,
if it is, it must digitally sign the message with its private RSA key and send the signed message back to the
client. The client must then be able to verify that the server signed the message by using the server’s public
RSA key.
• Finally, the client must be able to send a Protected/Mashify get request to the server with an API Key in the
header which links to an Admin role user, and three parameters comprising:
o A string of text, encrypted using the server’s public RSA key
o A symmetric key (using AES encryption), encrypted using the server’s public RSA key
o An IV (initialization vector) for the symmetric key, also encrypted using the server’s public RSA key
If the server receives this request, it must…
o check to see if the API Key is in the database and whether the role is Admin,
o if it is, it must decrypt all three parameters using its private RSA key.
o It must then ‘mashify’ the string it was given (see task 14), encrypt the mashified string using the
client’s symmetric (AES) key and IV and
o finally, it must send the newly encrypted string back to the client.
The client must then be able to…
o decrypt the string using its symmetric (AES) key and IV and
o finally, output the new mashified string to the console.
Your task is to follow the next instructions carefully and develop a console-based client and Web API-Based server with
a Code First Entity Framework Database.
➢ You must use the skeleton solution as your starting point, and
➢ You must ensure that you carefully follow instructions on request types and names, parameter naming,
responses and response types – if you do not conform to these instructions, some of the marking tools will
not be able to find your code and you will receive a mark of 0 in affected areas.
Please do not, under any circumstances, publish your work on a public source code repository – even
after you leave the University. We have had issues in the past with students using public repositories (e.g.
on GitHub) and their work being plagairised. In these circumstances it becomes very difficult to ascertain
who was cheating and both parties may be penalised for collusion under the academic misconduct policy,
which can have severe academic consequences. Furthermore, the skeleton code remains the intellectual
property of the University of Hull and you are not permitted to share this publicly. You may, however, use
any of the solution in your future work and share your solution (including the skeleton code) privately (e.g.
a repository set to private) with prospective employers, etc., as you wish.
3
Instructions
Download the skeleton solution from Canvas and unzip it. You may delete or modify any of the code that has already
been written if you want, but for the most part you will only need to add to it. Note how there are a number of regions
and comments that identify tasks by number – use these as a guide to identify where you might want to add your code.
The Server
The server that you have been given in the skeleton solution is an ASP.NET Web API. You may find it useful to refer to
the Microsoft documentation on Web API. You should also recognise it from labs and lecture material.
There are a few things you should look at before you start working on the server:
1. Open TalkBackController.cs – Gribbald started writing this controller before they mysteriously disappeared. It
contains two get request actions for /API/TalkBack/Hello and /API/Talkback/Sort. Task1 will be to complete
these methods, but they should also be a guide to help you get started – you may change any of the code you
feel needs to be changed.
2. Open Auth->CustomAuthenticationHandler.cs – this is custom authentication middleware that is added inside
the ConfigureServices method inside Startup.cs. This means that whenever an HTTP request is made, this
method will run before the request gets to your controller. You will need to modify this method in TASK5 to
verify that the API Key in the header is correct and authenticate the user. There is a similar
CustomAuthorizationHandler – middleware that runs immediately after authentication if the authentication
succeeds. You will need to make changes to this code in TASK6. The use of these handlers is directly linked
to the Authorize attribute.
3. Open BaseController.cs – you should note four things:
a. This is an abstract class. You cannot make an instance of this controller.
b. It inherits from ControllerBase and has the attribute ApiController – all Controllers need to descend
from ControllerBase and have the attribute ApiController.
c. It stores a protected instance of the Database Context that you’ll need to use for your data access.
d. The route mapping has been changed to api/{controller}/{action} which will allow you to call
actions inside your controller (e.g. /API/TalkBack/Hello, where TalkBack is the controller and Hello
is the action).
BaseController is an abstract base class you should inherit from to retain the above functionality in your
controllers. If you open TalkbackController.cs you can see that it inherits from BaseController. To make your
life easier use this as a template for any new controllers you create.
Before you start working on your server, you may find it useful to use a tool which allows you to craft requests (with
control over the header/body/URI/etc.) send them to your server for debugging purposes and receive RESTful
responses. This means you won’t need to have a working client to test your server out. A suitable (and free) tool is
PostMan: https://www.getpostman.com/apps
Finally, I strongly suggest you use the test server (prototype). This server responds as per this coursework
specification. If your server responds in the same way as the test then you are likely to get a good mark for the
server elements of this coursework and if your client works properly with the test server then you can be confident
it is working properly too. You may use Postman to analyse the responses from the test server before you start
producing your server. There’s more info at the end of TASK10 but the test server can be accessed by pointing
your client to:
http://150.237.94.9//
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed to you via email already but if you have not received this
then email me: john.dixon@hull.ac.uk
4
Tasks
The following tasks are designed to help you identify exactly what to do to meet the
specification and they give a logical order for doing it too. You must ensure that your
server and client respond exactly as specified to get the marks for that section, so
read this specification very carefully. Most tasks also have a section dedicated to
testing so that you can test your solution against expected results. If your server and
client do not respond exactly as specified you will not receive marks for the
corresponding marking criteria.
Task1: [3 marks]
When the client makes a get request for api/TalkBack/Hello or api/TalkBack/Sort, the talkback controller must
handle the responses.
Complete these methods so that they offer the responses detailed in the specification.
Once you have created these methods, right-click the WebAPI project, select Debug and click Start New Instance.
This should fire up IIS, open your browser and take you to a web page with an address similar to localhost:24702
where 24702 is your portnumber. Identify what this port number is as it will be required in your client and for
testing.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace with
your port number) of:
For Hello: localhost:/api/talkback/hello
Should return "Hello World" in the body of the result with a status code of OK (200)
For Sort: localhost:/api/talkback/sort?integers=8&integers=2&integers=5
Should return [2,5,8] in the body of the result with a status code of OK (200)
If there are no integers submitted, the result should be [] with a status code of OK (200)
If the submitted integers are invalid (e.g. a char is submitted) the server should return with a with a status code of BAD
REQUEST (400)
Task2:
In order to work with databases, you have been asked to use Entity Framework Core.
Gribbald has already put all of the references, etc. that you will need into the project. You just need to define a
User class.
Open User.cs, where you will find the Task2 region. Complete the User class with:
• An empty constructor
• A public string ApiKey property (which is the unique database key and is the API Key for the user)
• A public string UserName property (which is the username of the user)
• A public string or enum Role property (which saves the role of the user)
UserContext.cs has been created for you already, but to create the database correctly you’ll need to generate a
migration*.
*You may first need to set the project directory by typing cd DistSysAcw into the Package Manager Console
5
Task3:
You should keep your controller classes only loosely coupled to the database access code. Ensuring your code is
nicely abstracted is good coding practice as it will allow you to easily swap out the data access logic in the future
without editing your controllers. You may like to create these data access functions now.
For the next tasks, you may need database access such as:
1. Create a new user, using a username given as a parameter and creating a new GUID which is saved as a
string to the database as the ApiKey. This must return the ApiKey or the User object so that the server can
pass the Key back to the client.
2. Check if a user with a given ApiKey string exists in the database, returning true or false.
3. Check if a user with a given ApiKey and UserName exists in the database, returning true or false.
4. Check if a user with a given ApiKey string exists in the database, returning the User object.
5. Delete a user with a given ApiKey from the database
Hint: The BaseController already contains an instance of UserContext
Consider: What happens in your solution if two admin users simultaneously try to change a user’s role?
Task4:
When the client makes a api/User/New get request or api/User/New post request, the server must be able to
handle them and provide a response.
Create a new controller called UserController. The easiest way to do this is by right-clicking on Controllers and
adding a new, Empty API Controller. You may want to inspect the existing TalkBack Controller to help set up your
new controller – note it’s base type, constructor and routing.
Both of these actions have the name ‘New’ but one is a GET request that takes its parameter from the URI and
the other is a POST request that takes a JSON string parameter from the body. The POST request must use a
content-type of application/json. Note the difference between a JSON string and a JSON Object with a string.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace with
your port number) of:
For GET: localhost:/api/user/new?username=UserOne
If a user with the username ‘UserOne’ exists in the database, the server should return "True - User Does Exist!
Did you mean to do a POST to create a new user?" in the body of the result with a status code of OK (200)
If a user with the username ‘UserOne’ does not exist in the database, the server should return "False - User Does
Not Exist! Did you mean to do a POST to create a new user?" in the body of the result with a status
code of OK (200).
If there is no string submitted, the server should return "False - User Does Not Exist! Did you mean to do
a POST to create a new user?" in the body of the result with a status code of OK (200).
For POST: localhost:/api/user/new with only “UserOne” in the body of the request
Should create a new User with the username ‘UserOne’, generate a new GUID as the user’s API Key, and then add the
new user to the database. Finally, the server should return the API Key as a string to the client with a status code of OK
(200). If this is the first user they should be saved as Admin role, otherwise just with User role.
If there is no string submitted in the body, the result should be "Oops. Make sure your body contains a string
with your username and your Content-Type is Content-Type:application/json" with a status code
of BAD REQUEST (400)
If the username is alrady taken, the result should be "Oops. This username is already in use. Please try
again with a new username." with a status code of FORBIDDEN (403)
6
Task5:
The client now has the ability to ask for an API Key, so our server will now need to be able to determine if a request
has a valid API Key in its header. A useful way to do this is to use an Authentication Scheme. Ours is a custom
Authentication Scheme so we can investige its functionality. Return to CustomAuthenticationHandler.cs. You must
add code to HandleAuthenticateAsync which tries to get the header ‘ApiKey’, and if it does exist, checks the
database to determine if the given API Key is valid. You should use your UserDatabaseAccess class that you
created in TASK3 to do the database access to loosen your coupling.
If the API Key is valid, you must get the relevant User from your database and set up a:
• Claim of type ClaimTypes.Name, using the user’s UserName as the string value
• Claim of type ClaimTypes.Role, using the user’s Role as the string value
• ClaimsIdentity with an authentication type of “ApiKey”, using an array containing both of your Claims
• ClaimsPrinciple, using the ClaimsIdentity above.
Finally, you must create an AuthenticationTicket using the ClaimsPrinciple and the scheme name (you can get this
using the property: this.Scheme.Name). You can now use the ticket to create a Success AuthenticateResult and
return as a Task.
If this is working, you should be able to use attributes such as [Authorize(Roles = "Admin")] or
[Authorize(Roles = "Admin, User")] to authorise requests which require a valid API key to be present.
If a user is not found, return a Fail AuthenticateResult and pass a 401 error with the JSON string "Unauthorized.
Check ApiKey in Header is correct." back to the user (see HandleChallengeAsync).
Task6:
So far we haven’t used any of our Roles. Before we can do this we’ll need to check that our users have the roles
they are supposed to. We are already looking at whether or not they have a valid API key (Task5), and we’re using
that easily by applying an attribute, so we’ll use a filter to modify the response.
Most of the filter has already been written. Open CustomAuthorizationHandler.cs and modify the code so that,
when the action requires a user to be in Admin role ONLY (e.g. [Authorize(Roles = "Admin")]) and the user
does not have the Admin role, you return a Forbidden status (403) with the message: "Forbidden. Admin access
only.". You will need the injected IHttpContextAccessor for this.
Task7:
Add to your UserController a method to handle an api/User/RemoveUser DELETE request.
• A user with role User or Admin should be able to use this request.
• The client must send its API Key in the header and a string username in the URI.
If the server receives this request, it must extract the ApiKey string from the header to see if the API Key is in the
database and, if it is, it must check that the username and API Key are the same user and if they are, it must
delete this user from the database. You should probably use your UserDatabaseAccess class that you created in
TASK3 to do the database access.
This method must return a Boolean value only. If a user has been deleted, the server must return true, otherwise,
the server must return false. In both cases, the server must return a status code of OK (200).
For your personal testing you can identify if your server is working by sending a delete request with the URI (replace
with your port number and with a username) of:
RemoveUser: localhost:/api/user/removeuser?username= with an ApiKey in the header of
the request
Should return true if the ApiKey and username match, are valid, and a user has been deleted or false if not.
7
Task8:
Inside UserController Add the api/User/ChangeRole method.
This method must only be accessible to users who are authorised by API key and have the role of Admin.
Update the role for the given username to the role provided (User or Admin).
The body should contain a JSON object in the form (where is the given username string and
is the given role string):
{ "username":, "role": }
For your personal testing you can identify if your server is working by sending a post request with the URI (replace with
your port number) of:
ChangeRole: localhost:/api/user/changerole with an ApiKey in the header of the request, a string username
in the body and a string role in the body
If success: Should return "DONE" in the body of the result, with a status code of OK (200)
If username does not exist: Should return "NOT DONE: Username does not exist" in the body of the result, with
a status code of BAD REQUEST (400)
If role is not User or Admin: Should return "NOT DONE: Role does not exist" in the body of the result, with a
status code of BAD REQUEST (400)
In all other error cases: Should return "NOT DONE: An error occured" in the body of the result, with a status code
of BAD REQUEST (400)
Task9:
Create a new ProtectedController. Add the api/Protected/Hello method, api/Protected/SHA1 method and
api/Protected/SHA256 method.
All of these requests must be authorised (User or Admin role) and all three must return strings to the client.
You may use the .NET SHA1 and SHA256 types for SHA1 and SHA256 hashing respectively.
Both SHA1 and SHA256 methods must take a string message from the URI and both must return the hexadecimal
hash as a string with no additional characters (e.g. no delimiters like -)
For your personal testing you can identify if your server is working by sending a get request with the URI (replace with
your port number) of:
For Hello: localhost:/api/protected/hello with an ApiKey in the header of the request
Should return "Hello" in the body of the result, where UserName is the User’s UserName from the
database, with a status code of OK (200). E.g. “Hello UserOne”.
For SHA1: localhost:/api/protected/sha1?message=hello
Should return "AAF4C61DDCC5E8A2DABEDE0F3B482CD9AEA9434D" in the body of the result with a status code of OK
(200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
For SHA256: localhost:/api/protected/sha256?message=hello
Should return "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824" in the body of the
result with a status code of OK (200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
8
Task10:
If you have not already started, begin to write the client (in project DistSysAcwClient) now as the next server tasks
are the most difficult and you should get the basic functionality of your client working before you attempt them.
The client must be a console application. It would make sense to use System.Net.Http.HttpClient. You don’t need
to use HttpClient but your client must be able to perform all of the same functions. Your client must request and
expect a JSON response type.
When your client is opened it should show: “Hello. What would you like to do?” and wait for user input.
Below is what the client must be able to do…
Input String
from Console
Window
Example Example Request Response Client
Function
Output to
Console
Window
TalkBack Hello TalkBack Hello localhost:/api/talkback/
hello
string None Response
string
TalkBack Sort
TalkBack Sort [6,1,8,4,3] localhost:/api/talkback/
sort?integers=6&integers=1&integers=
8&integers=4&integers=3
int[] None Response
as a string
User Get
User Get UserOne localhost:/api/user/new
?username=UserOne
string None Response
string
User Post
User Post UserOne localhost:/api/user/new
with “UserOne” in the request body
string Check status
of response.
Store API Key
and
username as
variables if
response: OK
“Got API
Key” if OK,
else print
response
string.
User Set
User Set UserOne
004b620d-a523-4b1b8bdc-857d8a5541b9
None – this function is only in the client n/a Store given
API Key and
username as
variables
“Stored”
User Delete User Delete Client must get the locally stored
username and ApiKey. If they don’t yet
exist the console must print “You need
to do a User Post or User Set first”.
localhost:/api/user/remo
veuser?username=
(with in the URI)
with ApiKey: in the header
Boolean None “True” if
delete
succeeded,
otherwise
“False”
User Role
User Role UserOne
Admin
Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:/api/user/chan
gerole
with ApiKey: in the header
and with the JSON object:
{
“username” : “UserOne”,
“role” : “Admin”
}
in the request body
string None Response
as a string
Protected Hello Protected Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
string None Response
string
9
localhost:/api/protected/
hello
with ApiKey: in the header
Protected SHA1
Protected SHA1 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:/api/protected/
sha1?message=Hello
with ApiKey: in the header
string None Response
String
Protected
SHA256
Protected SHA256 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
localhost:/api/protected/
sha256?message=Hello
with ApiKey: in the header
string None Response
String
If an error is returned, from your server, you must write out the error to the console or write out the given error
string if one is specified in the Output to Console Window column. The Console must output error messages and
never crash.
The client application should be asynchronous. The console must write “…please wait…” to the console window as
the request is sent and then write the output once the asynchronous Task has been completed. After the output
has been written, the console must write (on a new line) “What would you like to do next?”. When a new input is
entered, the Console Window must be cleared.
If the user enters “Exit” (without quotes), the console must close.
Whilst you do not have to, you may wish to have the console read/write to a file to save a valid UserName and
ApiKey, so that you don’t have to do a User Post or User Set every time you restart your application to test any of
the requests that require a username or ApiKey. If you do this, you should ensure that the software will run on
another computer.
Hint: you may find some of the information on this page useful: https://docs.microsoft.com/enus/dotnet/csharp/tutorials/console-webapiclient
10
**TEST SERVER**
As specified previously in this document, there is a test server from which to test your client and check that your
server responds in the same way. This test server acts as the ‘gold standard’. If your server responds in the same
way you can be certain that you will get the associated mark(s). You are very much encouraged to use the test
server as you will receive no marks for client functionality which does not work against the test server (even if it
works against your server).
I would very much advise you to create a single place in your client-side code (e.g. a const) which stores the first
part of your URL and then allows you to revert to using your server by changing only one line or config file.
The test domain is: http://150.237.94.9//
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed via email but if you have not received this or you find a bug
in the test server prototype then please email me: john.dixon@hull.ac.uk. I would recommend you don’t share your
code as it provides data isolation - someone else editing your data may interfere with your testing.
You should ensure that when you hand in your solution it is configured to access and use your local server, not
the test server.
➢ There is an additional useful command on the test server. You can run this command from any browser or
from Postman so you don’t need to implement it into your Client:
http://150.237.94.9//Api/Other/Clear
This command clears all users and logs from your account on the test server, refreshing it back to empty. This may
help during testing. You do not need to replicate this command on your server/client unless you want to; there are
no marks for other/clear.
11
Task11:
First, set up an RSA Crypto Service Provider which is configured once and the same across all threads. You must
not make a new key pair for each client but you may generate a new key pair each time the server starts. The
private RSA key must be stored securely (it would be a good idea to use the machine key store as we are using
Windows).
Now, returning the public key should be as simple as returning the XmlString created by your RSA Crypto Service
Provider, containing the public RSA key for your server.
This request must be authorised (User or Admin role).
On receiving an api/Protected/GetPublicKey get request, the server must check to see if the API Key is in the
database and, if it is, it must send back its RSA public key.
Remember not to send over the private key!!
For your personal testing you can identify if your server is working by sending a get request with the URI (replace with
your port number) of:
GetPublicKey: localhost:/api/protected/getpublickey with an ApiKey in the header of the request
Should return the XmlString containing the public key, which should look something like this:
"rSJEEeLC4H452XFL+taho/473M5OKdfSC/PKNFk55xOx/M5HbDxG9ihoENpazG7OHsGit
b0aXfn2qEVzhaaTtUJUKyO+sV2nQ6aaE0rwFUK0XxttFX1ann/d3qNTrlVxWdzygGq8ODn7qzvijcXjR/S2iyEguSNOuwhU
O/M98sk= AQAB "
Now add the functionality to your client which will allow it to store the public key. You can store the key however
you like, but it must be usable when you need to encrypt something to send it to the server.
Input String
from Console
Window
Example Request Response Client
Function
Output to
Console<
Distributed Systems ACW
2023/24
You have been hired as a distributed systems programmer after Gribbald, their previous expert disappeared one foggy
night. You’ve been promised that your first job for them won’t be very hard and, thankfully, it’s really not. The task is
to develop a client/server, using ASP.NET Core Web API, which provides a number of extra secret security and
encryption services… sort of.
You’re lucky though. Gribbald was working on this project before you and has already created an outline solution with
some ‘TODO’ regions, based on the original specification. They have also broken down the specification into tasks for
you, which should make your life even easier!
On your first day you were handed this specification:
• The server must be able to handle multiple client requests simultaneously.
• The server must use Entity Framework, Code First, to create and manage a local database of Users (that will,
in the future be moved over to a production database).
• The client must be able to get a TalkBack/Hello message from the server. The server will respond with “Hello
World”.
• The client must be able to send a TalkBack/Sort message as a get request to the server with an array of
integers as parameters. The server will sort the integers into ascending order and return the sorted array to
the client, because – well, sorting data is tedious.
• The client must be able to send a User/New get request to the server which has a username string as a
parameter. The server must return a string identifying if the username already exists in the database.
• The client must be able to send a User/New post request to the server which has a username string in the
request body. The server must create a new user, generate a new GUID as an API Key, save the user to the
database and return the API Key to the user. If this is the first user they should be saved as Admin role,
otherwise just with User role.
• The client must be able to send a User/RemoveUser delete request to the server with an API Key in the header
and a string username in the URI. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must check that the username and API Key are the same user and if they are, it
must delete this user from the database.
• The client must be able to send a User/ChangeRole Post request to the server with an API Key in the header
which links to an Admin role user, a string username in the body and a string role in the body. If the server
receives this request, it must check to see if the API Key is in the database and whether the role is Admin, if it
is, it must update the role for the given username to the role provided (User or Admin)
• The client must be able to send a Protected/Hello get request to the server with an API Key in the header. If
the server receives this request, it must check to see if the API Key is in the database and, if it is, it must get
the username associated with the API Key and send back “Hello
• The client must be able to send a Protected/SHA1 get request to the server with an API Key in the header and
a string message as a parameter. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must compute the SHA1 hash of the message and return it in hexadecimal form to
the client.
• Somebody told the boss that SHA256 is more secure than SHA1 so the client must be able to send a
Protected/SHA256 get request to the server with an API Key in the header and a string message as a
parameter. If the server receives this request, it must check to see if the API Key is in the database and, if it
is, it must compute the SHA256 hash of the message and return it in hexadecimal form to the client.
2
• The client must be able to send a Protected/GetPublicKey get request to the server with an API Key in the
header. If the server receives this request, it must check to see if the API Key is in the database and, if it is, it
must send back its RSA public key.
• The client must be able to send a Protected/Sign request with an API Key in the header and a string message
as a parameter. If the server receives this request, it must check to see if the API Key is in the database and,
if it is, it must digitally sign the message with its private RSA key and send the signed message back to the
client. The client must then be able to verify that the server signed the message by using the server’s public
RSA key.
• Finally, the client must be able to send a Protected/Mashify get request to the server with an API Key in the
header which links to an Admin role user, and three parameters comprising:
o A string of text, encrypted using the server’s public RSA key
o A symmetric key (using AES encryption), encrypted using the server’s public RSA key
o An IV (initialization vector) for the symmetric key, also encrypted using the server’s public RSA key
If the server receives this request, it must…
o check to see if the API Key is in the database and whether the role is Admin,
o if it is, it must decrypt all three parameters using its private RSA key.
o It must then ‘mashify’ the string it was given (see task 14), encrypt the mashified string using the
client’s symmetric (AES) key and IV and
o finally, it must send the newly encrypted string back to the client.
The client must then be able to…
o decrypt the string using its symmetric (AES) key and IV and
o finally, output the new mashified string to the console.
Your task is to follow the next instructions carefully and develop a console-based client and Web API-Based server with
a Code First Entity Framework Database.
➢ You must use the skeleton solution as your starting point, and
➢ You must ensure that you carefully follow instructions on request types and names, parameter naming,
responses and response types – if you do not conform to these instructions, some of the marking tools will
not be able to find your code and you will receive a mark of 0 in affected areas.
Please do not, under any circumstances, publish your work on a public source code repository – even
after you leave the University. We have had issues in the past with students using public repositories (e.g.
on GitHub) and their work being plagairised. In these circumstances it becomes very difficult to ascertain
who was cheating and both parties may be penalised for collusion under the academic misconduct policy,
which can have severe academic consequences. Furthermore, the skeleton code remains the intellectual
property of the University of Hull and you are not permitted to share this publicly. You may, however, use
any of the solution in your future work and share your solution (including the skeleton code) privately (e.g.
a repository set to private) with prospective employers, etc., as you wish.
3
Instructions
Download the skeleton solution from Canvas and unzip it. You may delete or modify any of the code that has already
been written if you want, but for the most part you will only need to add to it. Note how there are a number of regions
and comments that identify tasks by number – use these as a guide to identify where you might want to add your code.
The Server
The server that you have been given in the skeleton solution is an ASP.NET Web API. You may find it useful to refer to
the Microsoft documentation on Web API. You should also recognise it from labs and lecture material.
There are a few things you should look at before you start working on the server:
1. Open TalkBackController.cs – Gribbald started writing this controller before they mysteriously disappeared. It
contains two get request actions for /API/TalkBack/Hello and /API/Talkback/Sort. Task1 will be to complete
these methods, but they should also be a guide to help you get started – you may change any of the code you
feel needs to be changed.
2. Open Auth->CustomAuthenticationHandler.cs – this is custom authentication middleware that is added inside
the ConfigureServices method inside Startup.cs. This means that whenever an HTTP request is made, this
method will run before the request gets to your controller. You will need to modify this method in TASK5 to
verify that the API Key in the header is correct and authenticate the user. There is a similar
CustomAuthorizationHandler – middleware that runs immediately after authentication if the authentication
succeeds. You will need to make changes to this code in TASK6. The use of these handlers is directly linked
to the Authorize attribute.
3. Open BaseController.cs – you should note four things:
a. This is an abstract class. You cannot make an instance of this controller.
b. It inherits from ControllerBase and has the attribute ApiController – all Controllers need to descend
from ControllerBase and have the attribute ApiController.
c. It stores a protected instance of the Database Context that you’ll need to use for your data access.
d. The route mapping has been changed to api/{controller}/{action} which will allow you to call
actions inside your controller (e.g. /API/TalkBack/Hello, where TalkBack is the controller and Hello
is the action).
BaseController is an abstract base class you should inherit from to retain the above functionality in your
controllers. If you open TalkbackController.cs you can see that it inherits from BaseController. To make your
life easier use this as a template for any new controllers you create.
Before you start working on your server, you may find it useful to use a tool which allows you to craft requests (with
control over the header/body/URI/etc.) send them to your server for debugging purposes and receive RESTful
responses. This means you won’t need to have a working client to test your server out. A suitable (and free) tool is
PostMan: https://www.getpostman.com/apps
Finally, I strongly suggest you use the test server (prototype). This server responds as per this coursework
specification. If your server responds in the same way as the test then you are likely to get a good mark for the
server elements of this coursework and if your client works properly with the test server then you can be confident
it is working properly too. You may use Postman to analyse the responses from the test server before you start
producing your server. There’s more info at the end of TASK10 but the test server can be accessed by pointing
your client to:
http://150.237.94.9/
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed to you via email already but if you have not received this
then email me: john.dixon@hull.ac.uk
4
Tasks
The following tasks are designed to help you identify exactly what to do to meet the
specification and they give a logical order for doing it too. You must ensure that your
server and client respond exactly as specified to get the marks for that section, so
read this specification very carefully. Most tasks also have a section dedicated to
testing so that you can test your solution against expected results. If your server and
client do not respond exactly as specified you will not receive marks for the
corresponding marking criteria.
Task1: [3 marks]
When the client makes a get request for api/TalkBack/Hello or api/TalkBack/Sort, the talkback controller must
handle the responses.
Complete these methods so that they offer the responses detailed in the specification.
Once you have created these methods, right-click the WebAPI project, select Debug and click Start New Instance.
This should fire up IIS, open your browser and take you to a web page with an address similar to localhost:24702
where 24702 is your portnumber. Identify what this port number is as it will be required in your client and for
testing.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace
your port number) of:
For Hello: localhost:
Should return "Hello World" in the body of the result with a status code of OK (200)
For Sort: localhost:
Should return [2,5,8] in the body of the result with a status code of OK (200)
If there are no integers submitted, the result should be [] with a status code of OK (200)
If the submitted integers are invalid (e.g. a char is submitted) the server should return with a with a status code of BAD
REQUEST (400)
Task2:
In order to work with databases, you have been asked to use Entity Framework Core.
Gribbald has already put all of the references, etc. that you will need into the project. You just need to define a
User class.
Open User.cs, where you will find the Task2 region. Complete the User class with:
• An empty constructor
• A public string ApiKey property (which is the unique database key and is the API Key for the user)
• A public string UserName property (which is the username of the user)
• A public string or enum Role property (which saves the role of the user)
UserContext.cs has been created for you already, but to create the database correctly you’ll need to generate a
migration*.
*You may first need to set the project directory by typing cd DistSysAcw into the Package Manager Console
5
Task3:
You should keep your controller classes only loosely coupled to the database access code. Ensuring your code is
nicely abstracted is good coding practice as it will allow you to easily swap out the data access logic in the future
without editing your controllers. You may like to create these data access functions now.
For the next tasks, you may need database access such as:
1. Create a new user, using a username given as a parameter and creating a new GUID which is saved as a
string to the database as the ApiKey. This must return the ApiKey or the User object so that the server can
pass the Key back to the client.
2. Check if a user with a given ApiKey string exists in the database, returning true or false.
3. Check if a user with a given ApiKey and UserName exists in the database, returning true or false.
4. Check if a user with a given ApiKey string exists in the database, returning the User object.
5. Delete a user with a given ApiKey from the database
Hint: The BaseController already contains an instance of UserContext
Consider: What happens in your solution if two admin users simultaneously try to change a user’s role?
Task4:
When the client makes a api/User/New get request or api/User/New post request, the server must be able to
handle them and provide a response.
Create a new controller called UserController. The easiest way to do this is by right-clicking on Controllers and
adding a new, Empty API Controller. You may want to inspect the existing TalkBack Controller to help set up your
new controller – note it’s base type, constructor and routing.
Both of these actions have the name ‘New’ but one is a GET request that takes its parameter from the URI and
the other is a POST request that takes a JSON string parameter from the body. The POST request must use a
content-type of application/json. Note the difference between a JSON string and a JSON Object with a string.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace
your port number) of:
For GET: localhost:
If a user with the username ‘UserOne’ exists in the database, the server should return "True - User Does Exist!
Did you mean to do a POST to create a new user?" in the body of the result with a status code of OK (200)
If a user with the username ‘UserOne’ does not exist in the database, the server should return "False - User Does
Not Exist! Did you mean to do a POST to create a new user?" in the body of the result with a status
code of OK (200).
If there is no string submitted, the server should return "False - User Does Not Exist! Did you mean to do
a POST to create a new user?" in the body of the result with a status code of OK (200).
For POST: localhost:
Should create a new User with the username ‘UserOne’, generate a new GUID as the user’s API Key, and then add the
new user to the database. Finally, the server should return the API Key as a string to the client with a status code of OK
(200). If this is the first user they should be saved as Admin role, otherwise just with User role.
If there is no string submitted in the body, the result should be "Oops. Make sure your body contains a string
with your username and your Content-Type is Content-Type:application/json" with a status code
of BAD REQUEST (400)
If the username is alrady taken, the result should be "Oops. This username is already in use. Please try
again with a new username." with a status code of FORBIDDEN (403)
6
Task5:
The client now has the ability to ask for an API Key, so our server will now need to be able to determine if a request
has a valid API Key in its header. A useful way to do this is to use an Authentication Scheme. Ours is a custom
Authentication Scheme so we can investige its functionality. Return to CustomAuthenticationHandler.cs. You must
add code to HandleAuthenticateAsync which tries to get the header ‘ApiKey’, and if it does exist, checks the
database to determine if the given API Key is valid. You should use your UserDatabaseAccess class that you
created in TASK3 to do the database access to loosen your coupling.
If the API Key is valid, you must get the relevant User from your database and set up a:
• Claim of type ClaimTypes.Name, using the user’s UserName as the string value
• Claim of type ClaimTypes.Role, using the user’s Role as the string value
• ClaimsIdentity with an authentication type of “ApiKey”, using an array containing both of your Claims
• ClaimsPrinciple, using the ClaimsIdentity above.
Finally, you must create an AuthenticationTicket using the ClaimsPrinciple and the scheme name (you can get this
using the property: this.Scheme.Name). You can now use the ticket to create a Success AuthenticateResult and
return as a Task.
If this is working, you should be able to use attributes such as [Authorize(Roles = "Admin")] or
[Authorize(Roles = "Admin, User")] to authorise requests which require a valid API key to be present.
If a user is not found, return a Fail AuthenticateResult and pass a 401 error with the JSON string "Unauthorized.
Check ApiKey in Header is correct." back to the user (see HandleChallengeAsync).
Task6:
So far we haven’t used any of our Roles. Before we can do this we’ll need to check that our users have the roles
they are supposed to. We are already looking at whether or not they have a valid API key (Task5), and we’re using
that easily by applying an attribute, so we’ll use a filter to modify the response.
Most of the filter has already been written. Open CustomAuthorizationHandler.cs and modify the code so that,
when the action requires a user to be in Admin role ONLY (e.g. [Authorize(Roles = "Admin")]) and the user
does not have the Admin role, you return a Forbidden status (403) with the message: "Forbidden. Admin access
only.". You will need the injected IHttpContextAccessor for this.
Task7:
Add to your UserController a method to handle an api/User/RemoveUser DELETE request.
• A user with role User or Admin should be able to use this request.
• The client must send its API Key in the header and a string username in the URI.
If the server receives this request, it must extract the ApiKey string from the header to see if the API Key is in the
database and, if it is, it must check that the username and API Key are the same user and if they are, it must
delete this user from the database. You should probably use your UserDatabaseAccess class that you created in
TASK3 to do the database access.
This method must return a Boolean value only. If a user has been deleted, the server must return true, otherwise,
the server must return false. In both cases, the server must return a status code of OK (200).
For your personal testing you can identify if your server is working by sending a delete request with the URI (replace
with your port number and
RemoveUser: localhost:
the request
Should return true if the ApiKey and username match, are valid, and a user has been deleted or false if not.
7
Task8:
Inside UserController Add the api/User/ChangeRole method.
This method must only be accessible to users who are authorised by API key and have the role of Admin.
Update the role for the given username to the role provided (User or Admin).
The body should contain a JSON object in the form (where
is the given role string):
{ "username":
For your personal testing you can identify if your server is working by sending a post request with the URI (replace
your port number) of:
ChangeRole: localhost:
in the body and a string role in the body
If success: Should return "DONE" in the body of the result, with a status code of OK (200)
If username does not exist: Should return "NOT DONE: Username does not exist" in the body of the result, with
a status code of BAD REQUEST (400)
If role is not User or Admin: Should return "NOT DONE: Role does not exist" in the body of the result, with a
status code of BAD REQUEST (400)
In all other error cases: Should return "NOT DONE: An error occured" in the body of the result, with a status code
of BAD REQUEST (400)
Task9:
Create a new ProtectedController. Add the api/Protected/Hello method, api/Protected/SHA1 method and
api/Protected/SHA256 method.
All of these requests must be authorised (User or Admin role) and all three must return strings to the client.
You may use the .NET SHA1 and SHA256 types for SHA1 and SHA256 hashing respectively.
Both SHA1 and SHA256 methods must take a string message from the URI and both must return the hexadecimal
hash as a string with no additional characters (e.g. no delimiters like -)
For your personal testing you can identify if your server is working by sending a get request with the URI (replace
your port number) of:
For Hello: localhost:
Should return "Hello
database, with a status code of OK (200). E.g. “Hello UserOne”.
For SHA1: localhost:
Should return "AAF4C61DDCC5E8A2DABEDE0F3B482CD9AEA9434D" in the body of the result with a status code of OK
(200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
For SHA256: localhost:
Should return "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824" in the body of the
result with a status code of OK (200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
8
Task10:
If you have not already started, begin to write the client (in project DistSysAcwClient) now as the next server tasks
are the most difficult and you should get the basic functionality of your client working before you attempt them.
The client must be a console application. It would make sense to use System.Net.Http.HttpClient. You don’t need
to use HttpClient but your client must be able to perform all of the same functions. Your client must request and
expect a JSON response type.
When your client is opened it should show: “Hello. What would you like to do?” and wait for user input.
Below is what the client must be able to do…
Input String
from Console
Window
Example Example Request Response Client
Function
Output to
Console
Window
TalkBack Hello TalkBack Hello localhost:
hello
string None Response
string
TalkBack Sort
TalkBack Sort [6,1,8,4,3] localhost:
sort?integers=6&integers=1&integers=
8&integers=4&integers=3
int[] None Response
as a string
User Get
User Get UserOne localhost:
?username=UserOne
string None Response
string
User Post
User Post UserOne localhost:
with “UserOne” in the request body
string Check status
of response.
Store API Key
and
username as
variables if
response: OK
“Got API
Key” if OK,
else print
response
string.
User Set
User Set UserOne
004b620d-a523-4b1b8bdc-857d8a5541b9
None – this function is only in the client n/a Store given
API Key and
username as
variables
“Stored”
User Delete User Delete Client must get the locally stored
username and ApiKey. If they don’t yet
exist the console must print “You need
to do a User Post or User Set first”.
localhost:
veuser?username=
(with
with ApiKey:
Boolean None “True” if
delete
succeeded,
otherwise
“False”
User Role
User Role UserOne
Admin
Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:
gerole
with ApiKey:
and with the JSON object:
{
“username” : “UserOne”,
“role” : “Admin”
}
in the request body
string None Response
as a string
Protected Hello Protected Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
string None Response
string
9
localhost:
hello
with ApiKey:
Protected SHA1
Protected SHA1 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:
sha1?message=Hello
with ApiKey:
string None Response
String
Protected
SHA256
Protected SHA256 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
localhost:
sha256?message=Hello
with ApiKey:
string None Response
String
If an error is returned, from your server, you must write out the error to the console or write out the given error
string if one is specified in the Output to Console Window column. The Console must output error messages and
never crash.
The client application should be asynchronous. The console must write “…please wait…” to the console window as
the request is sent and then write the output once the asynchronous Task has been completed. After the output
has been written, the console must write (on a new line) “What would you like to do next?”. When a new input is
entered, the Console Window must be cleared.
If the user enters “Exit” (without quotes), the console must close.
Whilst you do not have to, you may wish to have the console read/write to a file to save a valid UserName and
ApiKey, so that you don’t have to do a User Post or User Set every time you restart your application to test any of
the requests that require a username or ApiKey. If you do this, you should ensure that the software will run on
another computer.
Hint: you may find some of the information on this page useful: https://docs.microsoft.com/enus/dotnet/csharp/tutorials/console-webapiclient
10
**TEST SERVER**
As specified previously in this document, there is a test server from which to test your client and check that your
server responds in the same way. This test server acts as the ‘gold standard’. If your server responds in the same
way you can be certain that you will get the associated mark(s). You are very much encouraged to use the test
server as you will receive no marks for client functionality which does not work against the test server (even if it
works against your server).
I would very much advise you to create a single place in your client-side code (e.g. a const) which stores the first
part of your URL and then allows you to revert to using your server by changing only one line or config file.
The test domain is: http://150.237.94.9/
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed via email but if you have not received this or you find a bug
in the test server prototype then please email me: john.dixon@hull.ac.uk. I would recommend you don’t share your
code as it provides data isolation - someone else editing your data may interfere with your testing.
You should ensure that when you hand in your solution it is configured to access and use your local server, not
the test server.
➢ There is an additional useful command on the test server. You can run this command from any browser or
from Postman so you don’t need to implement it into your Client:
http://150.237.94.9/
This command clears all users and logs from your account on the test server, refreshing it back to empty. This may
help during testing. You do not need to replicate this command on your server/client unless you want to; there are
no marks for other/clear.
11
Task11:
First, set up an RSA Crypto Service Provider which is configured once and the same across all threads. You must
not make a new key pair for each client but you may generate a new key pair each time the server starts. The
private RSA key must be stored securely (it would be a good idea to use the machine key store as we are using
Windows).
Now, returning the public key should be as simple as returning the XmlString created by your RSA Crypto Service
Provider, containing the public RSA key for your server.
This request must be authorised (User or Admin role).
On receiving an api/Protected/GetPublicKey get request, the server must check to see if the API Key is in the
database and, if it is, it must send back its RSA public key.
Remember not to send over the private key!!
For your personal testing you can identify if your server is working by sending a get request with the URI (replace
your port number) of:
GetPublicKey: localhost:
Should return the XmlString containing the public key, which should look something like this:
"
b0aXfn2qEVzhaaTtUJUKyO+sV2nQ6aaE0rwFUK0XxttFX1ann/d3qNTrlVxWdzygGq8ODn7qzvijcXjR/S2iyEguSNOuwhU
O/M98sk=
Now add the functionality to your client which will allow it to store the public key. You can store the key however
you like, but it must be usable when you need to encrypt something to send it to the server.
Input String
from Console
Window
Example Request Response Client
Function
Output to
Console<