1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 package org.mozilla.gecko.fxa.login; 5 6 import org.junit.Assert; 7 import org.junit.Before; 8 import org.junit.Test; 9 import org.junit.runner.RunWith; 10 import org.mozilla.gecko.background.fxa.FxAccountClient; 11 import org.mozilla.gecko.background.fxa.FxAccountUtils; 12 import org.mozilla.gecko.background.testhelpers.TestRunner; 13 import org.mozilla.gecko.background.testhelpers.WaitHelper; 14 import org.mozilla.gecko.browserid.BrowserIDKeyPair; 15 import org.mozilla.gecko.browserid.RSACryptoImplementation; 16 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate; 17 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition; 18 import org.mozilla.gecko.fxa.login.State.StateLabel; 19 import org.mozilla.gecko.sync.Utils; 20 21 import java.security.NoSuchAlgorithmException; 22 import java.util.LinkedList; 23 24 @RunWith(TestRunner.class) 25 public class TestFxAccountLoginStateMachine { 26 // private static final String TEST_AUDIENCE = "http://testAudience.com"; 27 private static final String TEST_EMAIL = "test@test.com"; 28 private static byte[] TEST_EMAIL_UTF8; 29 private static final String TEST_PASSWORD = "testtest"; 30 private static byte[] TEST_PASSWORD_UTF8; 31 private static byte[] TEST_QUICK_STRETCHED_PW; 32 private static byte[] TEST_UNWRAPKB; 33 private static final byte[] TEST_SESSION_TOKEN = Utils.generateRandomBytes(32); 34 private static final byte[] TEST_KEY_FETCH_TOKEN = Utils.generateRandomBytes(32); 35 36 protected MockFxAccountClient client; 37 protected FxAccountLoginStateMachine sm; 38 39 @Before setUp()40 public void setUp() throws Exception { 41 if (TEST_EMAIL_UTF8 == null) { 42 TEST_EMAIL_UTF8 = TEST_EMAIL.getBytes("UTF-8"); 43 } 44 if (TEST_PASSWORD_UTF8 == null) { 45 TEST_PASSWORD_UTF8 = TEST_PASSWORD.getBytes("UTF-8"); 46 } 47 if (TEST_QUICK_STRETCHED_PW == null) { 48 TEST_QUICK_STRETCHED_PW = FxAccountUtils.generateQuickStretchedPW(TEST_EMAIL_UTF8, TEST_PASSWORD_UTF8); 49 } 50 if (TEST_UNWRAPKB == null) { 51 TEST_UNWRAPKB = FxAccountUtils.generateUnwrapBKey(TEST_QUICK_STRETCHED_PW); 52 } 53 client = new MockFxAccountClient(); 54 sm = new FxAccountLoginStateMachine(); 55 } 56 57 protected static class Trace { 58 public final LinkedList<State> states; 59 public final LinkedList<Transition> transitions; 60 Trace(LinkedList<State> states, LinkedList<Transition> transitions)61 public Trace(LinkedList<State> states, LinkedList<Transition> transitions) { 62 this.states = states; 63 this.transitions = transitions; 64 } 65 assertEquals(String string)66 public void assertEquals(String string) { 67 Assert.assertArrayEquals(string.split(", "), toString().split(", ")); 68 } 69 70 @Override toString()71 public String toString() { 72 final LinkedList<State> states = new LinkedList<State>(this.states); 73 final LinkedList<Transition> transitions = new LinkedList<Transition>(this.transitions); 74 LinkedList<String> names = new LinkedList<String>(); 75 State state; 76 while ((state = states.pollFirst()) != null) { 77 names.add(state.getStateLabel().name()); 78 Transition transition = transitions.pollFirst(); 79 if (transition != null) { 80 names.add(">" + transition.toString()); 81 } 82 } 83 return names.toString(); 84 } 85 stateString()86 public String stateString() { 87 LinkedList<String> names = new LinkedList<String>(); 88 for (State state : states) { 89 names.add(state.getStateLabel().name()); 90 } 91 return names.toString(); 92 } 93 transitionString()94 public String transitionString() { 95 LinkedList<String> names = new LinkedList<String>(); 96 for (Transition transition : transitions) { 97 names.add(transition.toString()); 98 } 99 return names.toString(); 100 } 101 } 102 trace(final State initialState, final StateLabel desiredState)103 protected Trace trace(final State initialState, final StateLabel desiredState) { 104 final LinkedList<Transition> transitions = new LinkedList<Transition>(); 105 final LinkedList<State> states = new LinkedList<State>(); 106 states.add(initialState); 107 108 WaitHelper.getTestWaiter().performWait(new Runnable() { 109 @Override 110 public void run() { 111 sm.advance(initialState, desiredState, new LoginStateMachineDelegate() { 112 @Override 113 public void handleTransition(Transition transition, State state) { 114 transitions.add(transition); 115 states.add(state); 116 } 117 118 @Override 119 public void handleFinal(State state) { 120 WaitHelper.getTestWaiter().performNotify(); 121 } 122 123 @Override 124 public FxAccountClient getClient() { 125 return client; 126 } 127 128 @Override 129 public long getCertificateDurationInMilliseconds() { 130 return 30 * 1000; 131 } 132 133 @Override 134 public long getAssertionDurationInMilliseconds() { 135 return 10 * 1000; 136 } 137 138 @Override 139 public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException { 140 return RSACryptoImplementation.generateKeyPair(512); 141 } 142 }); 143 } 144 }); 145 146 return new Trace(states, transitions); 147 } 148 149 @Test testEnagedUnverified()150 public void testEnagedUnverified() throws Exception { 151 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, false, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 152 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 153 trace.assertEquals("[Engaged, >AccountNeedsVerification, Engaged]"); 154 } 155 156 @Test testEngagedTransitionToAccountVerified()157 public void testEngagedTransitionToAccountVerified() throws Exception { 158 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 159 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", false, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 160 trace.assertEquals("[Engaged, >AccountVerified, Cohabiting, >LogMessage('sign succeeded'), Married]"); 161 } 162 163 @Test testEngagedVerified()164 public void testEngagedVerified() throws Exception { 165 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 166 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 167 trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >LogMessage('sign succeeded'), Married]"); 168 } 169 170 @Test testPartial()171 public void testPartial() throws Exception { 172 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 173 // What if we stop at Cohabiting? 174 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Cohabiting); 175 trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting]"); 176 } 177 178 @Test testBadSessionToken()179 public void testBadSessionToken() throws Exception { 180 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 181 client.sessionTokens.clear(); 182 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 183 trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >Log(<FxAccountClientRemoteException 401 [110]: invalid sessionToken>), Separated, >PasswordRequired, Separated]"); 184 } 185 186 @Test testBadKeyFetchToken()187 public void testBadKeyFetchToken() throws Exception { 188 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 189 client.keyFetchTokens.clear(); 190 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 191 trace.assertEquals("[Engaged, >Log(<FxAccountClientRemoteException 401 [110]: invalid keyFetchToken>), Separated, >PasswordRequired, Separated]"); 192 } 193 194 @Test testMarried()195 public void testMarried() throws Exception { 196 client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN); 197 Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married); 198 trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >LogMessage('sign succeeded'), Married]"); 199 // What if we're already in the desired state? 200 State married = trace.states.getLast(); 201 Assert.assertEquals(StateLabel.Married, married.getStateLabel()); 202 trace = trace(married, StateLabel.Married); 203 trace.assertEquals("[Married]"); 204 } 205 } 206