code-small.jpg

 

Cake Team Blogs

Cake Solutions architects, implements and maintains modern and scalable software, which includes server-side, rich browser applications and mobile development. Alongside the software engineering and delivery, Cake Solutions provides mentoring and training services. Whatever scale of system you ask us to develop, we will deliver the entire solution, not just lines of code. We appreciate the importance of good testing, Continuous Integration and delivery, and DevOps. We motivate, mentor and guide entire teams through modern software engineering. This enables us to deliver not just software, but to transform the way organisations think about and execute software delivery.

 

 

Purity and dirty secrets

Posted by Jan Machacek

Find me on:

11/11/12 14:04

I left a dirty secret lurking in my previous post about 2 phase authentication with text messages. I hope many of you felt uneasy reading the val secret = generateSecret line. The generateSecret thing has no place in purely functional code. It does some weird side-effect wizardry and computes values that are always different. Read on to find out how to keep tabs on the dirty secrets (and other IO) in your code.


The problem again

Recall the bit of code that generated the authentication token and the secret to be delivered to the user.

val token = UUID.randomUUID()
val secret = generateSecret

// save the token
create(AuthenticationToken(UUID.randomUUID(), token,
	new Date(), true, 2, Some(secret)))

// deliver the secret to the user
val deliveryAddress = DeliveryAddress(Some("447*******"), None)
second ! DeliverSecret(deliveryAddress, secret)

// we're logged in partially
sender ! Right(LoggedInPartially(token))

The lines that offends my sense of proper code are val token = UUID.randomUUID() and val secret = generateSecret. Let's pick one of them and dissect it. It says that secret is equal to generateSecret, therefore, it should be possible to replace secret with generateSecret in the code below.

Oh wait. We can't do that. The function generateSecret is not really a function at all. It does something and spouts out secrets. Just like the Daily Mail-o-matic. If we replace both occurrences of secret with generateSecret in the code above, it will fail. We must contain the side-effects.

The containment

We still need to perform these weird side-effects, but we need to contain them. We are going to use the type system with the assistance of the compiler to make sure we don't do anything funky. Instead of having a function that returns secrets as Strings, we'll have a function that returns boxes that carry the secret Strings. We will also notice that we can compose these boxes together to form bigger boxes.

Right-ho.

Thinking about this further, it turns out that it's not just the secret that's randomly generated, it is also the token number. In fact, we can expand the randomness to the entire AuthenticationToken. And so, we'll have the AuthenticationTokenGenerator.

private[authentication] trait AuthenticationTokenGenerator {

  def generateAuthenticationToken(userRef: UUID):
    IO[AuthenticationToken] =

    IO(AuthenticationToken(
    	userRef, UUID.randomUUID(), new Date(), false, 0, None))

  def generateSecret(token: AuthenticationToken):
    IO[AuthenticationToken] =

    IO(token.copy(
        retries = 2,
        secret = Some(UUID.randomUUID().toString.substring(0, 5)),
        partial = true))

}

Notice the return types of the generateAuthenticationToken and generateSecret. They are no longer the dirty, random AuthenticationToken values themselves, but boxes that carry the random values. This is rather important. Whenever we call generateAuthenticationToken, we get a box that carries the generated AuthenticationToken. And so, we cannot use it when we need just the AuthenticationToken. This leads us quite nicely to being able to wire these boxes together. I will show you the LoginActor again in its entirety:

/**
 * Login actor that supervises the actors in the login process.
 */
class LoginActor(secretDelivery: ActorRef) extends
  Actor with AuthenticationTokenGenerator with TokenOperations {

  import scalaz.syntax.monad._

  /**
   * Saves the token in some persistent store
   *
   * @param at the token to be saved
   * @return the IO of the saved token
   */
  def createToken(at: AuthenticationToken): IO[AuthenticationToken] =
  	IO(create(at))

  /**
   * Sends the secret to the user
   *
   * @param at the authentication token
   * @return the IO of the token
   */
  def deliverSeret(at: AuthenticationToken): IO[AuthenticationToken] = {
    val deliveryAddress = DeliveryAddress(None, Some("some@email.com"))

    secretDelivery ! DeliverSecret(deliveryAddress, at.secret.get)

    IO(at)
  }

  def receive = {
    case FirstLogin(username, password, clientSignature) =>
      // check that username & password are OK
      if (username == "root" && password == "p4ssw0rd") {
        val userRef = UUID.fromString("a3372060-2b3b-11e2-81c1-0800200c9a66")
        // the account is there, and needs 2nd phase auth

        val action = generateAuthenticationToken(userRef) >>=
                     generateSecret >>=
                     createToken >>=
                     deliverSeret >>=
                     { at => IO(sender ! Right(LoggedInPartially(at.token))) }

        action.unsafePerformIO()
      } else {
        // not hardcoded username or password, so...
        sender ! Left(BadUsernameOrPassword())
      }
    case SecondLogin(token, secret) =>
      find(token) match {
        case None =>
          sender ! Left(BadPartialToken())
        case Some(at) if !at.isValid(secret) && at.retries == 0 =>
          // no more retries
          delete(at.token)
          sender ! Left(TooManyBadSecrets())
        case Some(at) if !at.isValid(secret) && at.retries > 0 =>
          // bad secret, but retries still allowed
          update(at.copy(retries = at.retries - 1))
          sender ! Left(BadPartialToken())
        case Some(at) if at.isValid(secret) =>
          // delete the old one
          delete(at.token)

          val action = generateAuthenticationToken(at.userRef) >>=
                       createToken >>=
                       { at => IO(sender ! Right(LoggedInFully(at.token))) }

          action.unsafePerformIO()
      }
  }

}

So, ther you have it. We have carefully packaged up the side-effects into boxes and assembled the boxes together to get a bigger box. Together with the type checking we get from the compiler, we can ensure that we do not let dirty secrets escape into the rest of our codebase. And, I'm also happy that the = symbol means just that I can replace the symbol with whatever is on the right hand side. And the world is a bit nicer place.

Topics: Akka, Spray, Monads, Scalaz

Posts by Topic

see all

Subscribe to Email Updates




Videos

                                

Blog

                                

Case Studies


img24

Cake
Software

Find out more

img23

DevOps

Find out more

img24

Cake
Invest®

Find out more