The first edition source is available from .

The second edition will be , I need to write up my conclusions from this as a reference for later. This article is already too long, new content added to page 2

In August, I wrote this demo; then decided after the interview that some of the other bits of the work meant I wasn't appropriate.
In December, I am adding abit more work, for the Firewalls.

Transcript of the problem:

Create a PHP application for user registration, it doesn't need any views, we just need a REST API and JSON-only responses would be sufficient. Please incorporate the following requirements:

  • Users must have a user name
  • User names must be unique
  • Users cannot be deleted, they can only be disabled
  • Users must have at least 1 email address
  • User email addresses must adhere to RFC 822
  • User email addresses must be unique for enabled users
  • Users must have a password
  • User passwords must be stored securely
  • User passwords must be of at least 8 characters in length
  • User passwords must contain:-
    • at least one numerical digit
    • at least one uppercase character
    • at least one lowercase character
  • User passwords must not contain non alpha-numeric characters

An email confirmation application will access the user registration application - there is no need to develop this application. The email confirmation application must use an API key to access the user registration application.
The email confirmation application must be able to get email addresses that need to be confirmed. The email confirmation application needs to be able to confirm email addr

Architecture questions and notes

  • Do you have any requirements/details for the API keys? I have put a placeholder in place at present. The class has the relevant interface, although a I could put the static test value into Symfony. I am hiding the key from the URL, on purpose, so it never shows in the simple logs.
  • Should access to the API be restricted (e.g. identification of the sender)?
  • I understand the requirement of password protection, and can write notes on how to do this; but for demo, I am not using real data, or real servers. Eliminating the database reduces the cost of the demo.
  • I am proposing to solve this via Symfony2, as I was told in a call that there is sections of Symfony2 in the SOA. I would need to install & configure ZF2 otherwise. None of this will match the original ZF1 code base; but then again I don't propose to write anything in PHP4.4 either.
  • Alternatively the problem space would work very well with Node. I have read reviews (admittedly by Node enthusiasts) saying that Node out performed LAMP stacks.
  • I tend to use multiple asserts per test case, as a single function may need to be resolved to several logical tests (example a function that dumps a collection to disk would need to verify that: the right file is built, the right permissions are applied, the content is valid for that format and that it holds the right data ~ i.e. all the collections elements. It is faster to test the last two items separately.). Some OO people say this is bad. As the line numbers are reported for any failed assertion, there is no loss of information. Ideally nothing is committed with failures, so the problem is isolated to me.
  • The validation objects are built using Symfony code, but are not Symfony code. Having these separate allows testing to occur more quickly. My current solution may be ported other platforms without much effort. As this is a JSON interpreter, the standard HTTP forms don't work; and I am not currently using an ORM. A good OO solution, which lacks portability would be to extend the receiving/ parsing code with a JSON interpreter. Then use the standard form processors.
  • Testing the /user/add call gets annoying. As requested, I do actually check that the email is RFC-well-formed, that it isn't in use, and that the domain name exists.
  • Please don't try to run /user/edit to set suspended. It has its own call.
  • The edit feature is designed to take changes. If you don't change the username or email in a request, please don't submit them. This is much more practical than determining which other occurrence of the email exists in the system, and if this is logically the same user.
  • Logically there needs to be a set verified call...
  • I made some of the design in this very similar to the way that ZF1 does things (that is classes where the actual computation is very similar, but is held in a different class to encourage type safety.). This is intensional, and not the normal way to write code.
  • The out-side-in test script (Tests/manual), that I built to demo that the routes where correct is a quick script. It should be replaced with a Behat script, or similar web driver tool script. This is NOT to show the data validation rules are correct as these are done on the unit tests. I knew I would need to investigate how to create the abnormal HTTP request methods; so this seemed a faster solution.
  • In the JSONmessages, I am currently DI'ing the LegacyValidator, as I am unable to locate functioning notes to import the current Validator. The common solution of RecursiveValidator doesn't work for default config, with a fresh build of Symfony 2.5.3.
  • UPDATE: after I pushed this, I decided that the JSONmessage class wasn't really related to the Validator interface. I separated the chain of inheritance. I will move the class out of Validators in the next refactor, as its not a Validator.
  • UPDATE2: in discussion, the Validator end classes would be better if the common things like email population based validation was pulled out into separate calls (applied). Observers thought that I use terse local variables heavily, and longer names would communicate better. Loop variable are loop variables, although there are too many in that section of code. When code is properly factored, the local variables are trapped to fairly terse methods; and so the code is easy to read and manage.
  • UPDATE3: I started to build the auth layer. I have spent the time I have, and have published the second source bundle. This is not complete. This is not a good architecture; I either need to find the JSON-encoded REST bundle; or write it. I am unable to use standard classes in many places they expect data in forms not JSON objects. It is reasonable OO.
  • UPDATE4: People ask why I make a service do the work, rather than write out a controller. Mostly due to manual work to build controller tests. I'm still not sure that the more verbose “proper OO” solution is better. Would have been better to have a wider range of return code.
  • UPDATE5: Was looking at the code to do few cleanups, noticed I didn't set the HTTP return code (other than 200). This breaks HTTP caching tools, and isn't REST. I have added this now. In the same sweep of changes, I completed the security class unit-tests. I was trying to use the framework for this feature, so the unit-tests are too complicated. There is alot of dependencies for each of the classes.

Proposed calls:

I am aware that some of these verbs could be removed without loss of information, as the request method will differentiate them; but I think its more readable like this.

  • POST /user/add
  • PUT /user/suspend
  • PUT /user/edit
  • GET /user/verify ~ this lists email addresses to verify.
  • GET /user/show/{id} ~ display details attached to an ID.
  • POST /user/authenticate ~ do a user login, not a platform login.
  • PUT /edit/verified ~ sets the flag. Not in spec, but would be necessary.
  • V2 POST /authenticate ~ no GET needed, platform login

Add Sample

POST /user/add
...headers...

data={
    "api-key":"TESTTESTTEST",
    "username":"owen beresford",
    "email":"mrcool@hotmail.com",
    "password":"wHat3v3r"
    }

...headers...
data={
    "id":123,
    "status":0,
    "desc":"A new user was created."
    }

Suspend Sample

POST /user/suspend
...headers....

data={
    "api-key":"TESTTESTTEST",
    "id":123
    }

...headers...
data={
    "status":0,
    "desc":"Suspended user.",
    "id":123
    }

Edit Sample

PUT /user/edit
...headers....

data={
    "api-key":"TESTTESTTEST",
    "id":123,
    "password":"MoArsecur3id"
    }

...headers...
data={
    "status":0,
    "desc":"Edited user.",
    "id":123
    }

Verify Sample

GET /user/verify
...headers....

data={
    "api-key":"TESTTESTTEST",
    }

...headers...
data={
    "status":0,
    "desc":"List.",
    "emails":{
        "owen@server1.com",
        "owen@server2.com",
        "owen@server3.com",
        "owen@server4.com",
            }
    }

Show Sample

GET /user/show/123
...headers....

data={
    "api-key":"TESTTESTTEST",
    }

...headers...
data={
    "status":0,
    "desc":"Supplying user.",
    "username":"owen beresford",
    "email":"mrcool@hotmail.com",
    "password":"wHat3v3r",
    "id":123
    }

Authenticate Sample

POST /user/authenticate
...headers...
data={
    "api-key":"TESTTESTTEST",
    "email":"mrcool@hotmail.com",
    "password":"wHat3v3r"
    }

...headers...
data={
    "id":123,
    "status":0,
    "desc":"Successfully authenticated."
    }

Approach

The edition of this published in August was intensionally written mostly as my code. I didn't use FOSRest etc, so people could see my code ~ which as a demo, is important. The target company had a heterogeneous platform; and the ability to write my own work ~ as I understand the specifications ~ is useful. REST is a simple protocol, and I was mostly focussing on the software engineering in the business logic.

The bigger and younger edition is using FOS Rest 1, and demonstrate a different range of features. This is using a Symfony2 firewall for the user authentication. Whilst setting this up I notice demos for WSSE 2, which is another authentication protocol. In a consistent Symfony2 shop, it makes more sense to push features onto common libraries to increase efficiency of teamwork between developers (who probably will have already used this library). In the first version I am returning a plain text password in the API calls; this is necessary as my proposed password encryption/ obscuring hasn't been built.

The implementation for the user identification in the security classes is currently done twice. I was under the impression that I should be able to do the verification in the Event Listeners, for a minimal implementation. The get the library functions to work, I then had to add a range of additional classes. If I had been using a form POST, I could use normal Symfony libraries, and produces a good grade answer much faster (and no duplication). Sensible use would use FOS REST for JSON POSTS, and again involve writing less code.
The user identification in the firewalls is written to initially identify the user with the contents of a POST. The subsequent requests should include a HTTP header X-rest-session with the supplied auth key in it. This is the same basic idea as a session cookie, but won't get in the way of established libraries or mechanisms. This is not a cookie, as a previous implementation of this had problems with caching and domain names. The presence of the X-rest-session means the API key in the request is meaningless, and should be removed. It seems better semantics to have the firewall data outside the of the request; although you could say that that custom headers isn't REST. If I was building this as a thorough platform, I should probably allow the value as a cookie for that reason. The auth value is set to expire after a period, exactly like cookies do. This paragraph indicates that I was writing code faster than I was designing it; and I would state definitely true. My initial scope was to return this inside 24 hours.

An issue I have yet to resolve is data for PUT requests. The Symfony libraries allow access to the HTTP request body via Request->getContent(). For POSTs this may be read as many times as you wish. For PUTs the data is read from php://input, which is a stream and so only readable once. The first Request object made will trigger getContent() as part of the validation routines. In order for PUTs to function, the developers code must have access to the first object. I have a range of broken solutions at present, but will continue to address this, as it must be possible.

I will in time supply an entirely FOS solution, which is how I would do it for real code. Some of my security code is overly simplified. There are settings in the default FOS install that force the name of the firewall to 'secured_area'; I don't think this should be used in real code. The classes named Reference... currently omit unit tests, as they are copied from a tutorial, and have no decision making. This should be revised, but its better use of time to focus on the PUT requests, and the FOS implementation.

Install && Platform for first edition.

  • Currently no Symfony libraries need adding (there are no big documents in JSON e.g. >10MB, so there is little need to add a buffering class to parse the JSON with).
  • As this is source, without an installer, please ensure that “app/cache/json” exists as a directory. It will need permissions that the web user may read and write. Currently please also create an empty users.json file. A sample one is provided in icelineLtd/RESTDemoBundle/Tests/Fixtures/users.json; with test data.

Install && Platform for second edition.

Please add to your composer.json if absent:

  • “friendsofsymfony/rest-bundle”: “1.5.*@dev”

Things yet to add

  • The full feature /user/verify
  • The full feature /user/authenticate
  • A proper api-key system ~ wrote a mock, as received no guidance on requirements.
  • First layer validation on the HTTP request (mostly this is there a data attribute in the request)
  • Conceal the data storage. If I was using a database ~ a common thing for applications ~ I would make the passwords encrypted. The platform currently treats all data as transparent, and so returns passwords. As soon as the /user/authenticate call is made; that would change. A normal RDBMS feature is encryption, this code has no need to know what the passwords are.
  • If you change the number of email addresses, without changing the ones remaining; the code will reject you.
  • Request concurrency protection. As I would put the storage in a database for realistic use, I block this feature until then.
  • Change the session storage implementation in the test cases, so there are no errors reported.

Things done

  • The basic structure and interfaces.
  • Code is DRY, and by and large is SOLID.
  • Alot of tests. As I have added new features, I added tests where they had no place in my initial sketch.
  • Test and Code for /user/add, /user/show, /user/suspend and /user/edit
  • I have made the email address and name matching in lowercase as this is more useful.
  • The code does verify the uniqueness of emails and usernames.
  • The code does allow multiple emails per user.
  • Both the previous items occur simultaneously.
  • At the start of December I moved the JSONMessage out of the Validators and retested. This is the newer version stamp.
  • A reference implementation for Firewalls. The code is intentionally very simple to reduce dependencies.

References

  1. I like this. http://mmoreram.com/blog/2013/12/23/factory-pattern-in-symfony2/
  2. Why some of my code lacks tests, I didn't write it, no human did. http://mikeangstadt.name/projects/getter-setter-gen/
  3. My current best guess for email address validation is from http://stackoverflow.com/questions/46155/validate-email-address-in-javascripts

REST Symfony2 demo

RSS. Share: Share this resource on your twitter account. Share this resource on your linked-in account. G+

REST Symfony2 demo

RSS. Share: Share this resource on your linked-in account. Share this resource on your twitter account. G+ ­ Follow edited