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