Skip to content

Commit 27a2b3a

Browse files
mohitc1Copilot
andcommitted
feat: Phase 2 - Add DeviceRegistrationClientApplication, DeviceState, DiscoveryEndpoint
- Create DeviceRegistrationClientApplication in common (public API for OneAuth) - All methods take mandatory correlationId: UUID for IPC tracing - correlationId passed to V0 protocol parameters via constructor - Two constructors: simple (Context) and full (Context, components, discoveryClient, provider) - Add DeviceState enum in common4j (DEVICE_VALID, DEVICE_NOT_FOUND, DEVICE_DISABLED, UNKNOWN) - Add DiscoveryEndpoint enum in common4j (PROD, PPE, INT) - Update AbstractDeviceRegistrationProtocolParameters: remove no-arg constructor, add (UUID correlationId) constructor. Remove @AllArgsConstructor from V0 params, add explicit constructors with correlationId as first param. - Add DeviceRegistrationClientApplicationTest (7 tests) - Update serializer and packer tests for new constructor signatures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 43ffd79 commit 27a2b3a

21 files changed

Lines changed: 874 additions & 36 deletions

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
vNext
22
----------
3+
- [MINOR] Add DeviceRegistrationClientApplication as public API for OneAuth device registration with mandatory correlationId, DeviceState and DiscoveryEndpoint enums (#3073)
34
- [MINOR] Move device registration protocol types, domain types, controller, and packer from broker to common to enable OneAuth device registration support (#3066)
45
- [MINOR] Upgrade compileSdkVersion to 36 and buildToolsVersion to 36.0.0 (#3065)
56
- [PATCH] Rename SovSG to GovSG for the Singapore sovereign cloud identifiers (#3068)

common/src/main/java/com/microsoft/identity/deviceregistration/DeviceRegistrationClientApplication.kt

Lines changed: 408 additions & 0 deletions
Large diffs are not rendered by default.

common/src/test/java/com/microsoft/identity/deviceregistration/AndroidDeviceRegistrationClientControllerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class AndroidDeviceRegistrationClientControllerTest {
101101
private val testRecordWithAccount = DeviceRegistrationRecordWithAccount("account", "tenant", "upn", "deviceId", false, false)
102102

103103
private fun testParams() = TestHappyPathV0Parameters(
104-
"hello ", "world", true, false, 1.4f, 3.7f, testRecord, testRecordWithAccount
104+
UUID.randomUUID(), "hello ", "world", true, false, 1.4f, 3.7f, testRecord, testRecordWithAccount
105105
)
106106

107107
private fun testResponse() = TestHappyPathV0Response(

common/src/test/java/com/microsoft/identity/deviceregistration/AndroidDeviceRegistrationProtocolPackerTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,31 @@
2222
// THE SOFTWARE.
2323
package com.microsoft.identity.deviceregistration;
2424

25-
import static com.microsoft.identity.deviceregistration.java.exception.DeviceRegistrationException.INTERNAL_ERROR_CODE;
26-
import static com.microsoft.identity.deviceregistration.java.exception.DeviceRegistrationException.INVALID_PACKAGE_ERROR_CODE;
2725
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.CORRELATION_ID;
2826
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_DATA;
2927
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_EXCEPTION_ERROR_CAUSE_MESSAGE;
3028
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_EXCEPTION_ERROR_CODE;
3129
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_EXCEPTION_ERROR_MESSAGE;
3230
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_EXCEPTION_STACK_TRACE_STRING;
3331
import static com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker.PROTOCOL_NAME;
34-
32+
import static com.microsoft.identity.deviceregistration.java.exception.DeviceRegistrationException.INTERNAL_ERROR_CODE;
33+
import static com.microsoft.identity.deviceregistration.java.exception.DeviceRegistrationException.INVALID_PACKAGE_ERROR_CODE;
3534

3635
import android.os.Bundle;
3736

38-
import com.microsoft.identity.deviceregistration.testprotocols.TestHappyPathV0Parameters;
37+
import com.microsoft.identity.common.java.exception.BaseException;
38+
import com.microsoft.identity.common.java.exception.ClientException;
3939
import com.microsoft.identity.deviceregistration.java.api.DeviceRegistrationRecord;
4040
import com.microsoft.identity.deviceregistration.java.api.DeviceRegistrationRecordWithAccount;
4141
import com.microsoft.identity.deviceregistration.java.exception.DeviceRegistrationException;
4242
import com.microsoft.identity.deviceregistration.java.exception.DrsErrorResponseException;
43-
import com.microsoft.identity.common.java.exception.BaseException;
44-
import com.microsoft.identity.common.java.exception.ClientException;
43+
import com.microsoft.identity.deviceregistration.testprotocols.TestHappyPathV0Parameters;
4544

4645
import org.junit.Assert;
4746
import org.junit.Test;
4847
import org.junit.runner.RunWith;
4948
import org.robolectric.RobolectricTestRunner;
5049

51-
5250
import java.util.UUID;
5351

5452
import lombok.SneakyThrows;
@@ -72,6 +70,7 @@ public class AndroidDeviceRegistrationProtocolPackerTest {
7270

7371

7472
private static final TestHappyPathV0Parameters TEST_PROTOCOL = new TestHappyPathV0Parameters(
73+
UUID.randomUUID(),
7574
TEST_DATA,
7675
TEST_DATA,
7776
true,
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.deviceregistration
24+
25+
import android.content.Context
26+
import android.os.Bundle
27+
import androidx.test.core.app.ApplicationProvider
28+
import com.microsoft.identity.common.internal.activebrokerdiscovery.IBrokerDiscoveryClient
29+
import com.microsoft.identity.common.internal.broker.BrokerData
30+
import com.microsoft.identity.common.internal.broker.ipc.IIpcStrategy
31+
import com.microsoft.identity.common.java.interfaces.IPlatformComponents
32+
import com.microsoft.identity.deviceregistration.java.DeviceState
33+
import com.microsoft.identity.deviceregistration.java.api.DeviceRegistrationRecord
34+
import com.microsoft.identity.deviceregistration.java.api.IDeviceRegistrationRecord
35+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetDeviceRegistrationRecordV0Response
36+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetDeviceRegistrationRecordsV0Response
37+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetRegistrationStateV0Response
38+
import com.microsoft.identity.deviceregistration.java.protocol.response.PreProvisionedBlobV0Response
39+
import org.junit.Assert.assertEquals
40+
import org.junit.Assert.assertNotNull
41+
import org.junit.Assert.assertNull
42+
import org.junit.Before
43+
import org.junit.Test
44+
import org.junit.runner.RunWith
45+
import org.mockito.kotlin.any
46+
import org.mockito.kotlin.mock
47+
import org.mockito.kotlin.whenever
48+
import org.robolectric.RobolectricTestRunner
49+
import java.util.UUID
50+
import java.util.concurrent.Executors
51+
import java.util.concurrent.TimeUnit
52+
53+
/**
54+
* Unit tests for [DeviceRegistrationClientApplication].
55+
* Uses mock IPC strategy to verify DRCA methods call the controller
56+
* with correct V0 params and return correctly parsed responses.
57+
*/
58+
@RunWith(RobolectricTestRunner::class)
59+
class DeviceRegistrationClientApplicationTest {
60+
61+
private lateinit var context: Context
62+
private val backgroundExecutor = Executors.newSingleThreadExecutor()
63+
private val packer = AndroidDeviceRegistrationProtocolPacker()
64+
private val testBrokerPkg = "com.microsoft.test.broker"
65+
66+
@Before
67+
fun setup() {
68+
context = ApplicationProvider.getApplicationContext()
69+
}
70+
71+
private fun createDrca(strategy: IIpcStrategy): DeviceRegistrationClientApplication {
72+
val storageSupplier: com.microsoft.identity.common.java.interfaces.IStorageSupplier = mock(defaultAnswer = org.mockito.Mockito.RETURNS_DEEP_STUBS)
73+
val components: IPlatformComponents = mock {
74+
whenever(it.storageSupplier).thenReturn(storageSupplier)
75+
}
76+
val discoveryClient: IBrokerDiscoveryClient = mock {
77+
whenever(it.getActiveBroker(false)).thenReturn(BrokerData(testBrokerPkg, "sig"))
78+
}
79+
val provider = object : DeviceRegistrationIpcStrategiesProvider() {
80+
override fun getStrategies(
81+
context: Context,
82+
components: IPlatformComponents,
83+
activeBrokerPackageName: String
84+
): MutableList<IIpcStrategy> = mutableListOf(strategy)
85+
}
86+
return DeviceRegistrationClientApplication(context, components, discoveryClient, provider)
87+
}
88+
89+
private fun successStrategy(responseBundle: Bundle): IIpcStrategy {
90+
val strategy: IIpcStrategy = mock()
91+
whenever(strategy.getType()).thenReturn(IIpcStrategy.Type.CONTENT_PROVIDER)
92+
whenever(strategy.communicateToBroker(any())).thenReturn(responseBundle)
93+
return strategy
94+
}
95+
96+
private fun <T> runOnBackground(op: () -> T): T {
97+
val future = backgroundExecutor.submit(op)
98+
try {
99+
return future.get(5, TimeUnit.SECONDS)
100+
} catch (e: java.util.concurrent.ExecutionException) {
101+
throw e.cause ?: e
102+
}
103+
}
104+
105+
@Test
106+
fun getPreProvisionedBlob_returnsJws() {
107+
val expectedJws = "test.jws.blob"
108+
val response = PreProvisionedBlobV0Response(UUID.randomUUID(), expectedJws)
109+
val drca = createDrca(successStrategy(packer.pack(response)))
110+
111+
val result = runOnBackground {
112+
drca.getPreProvisionedBlob("test-tenant", UUID.randomUUID())
113+
}
114+
115+
assertEquals(expectedJws, result)
116+
}
117+
118+
@Test
119+
fun getRegistrationState_returnsDeviceState() {
120+
val response = GetRegistrationStateV0Response(UUID.randomUUID(), "DEVICE_VALID")
121+
val drca = createDrca(successStrategy(packer.pack(response)))
122+
123+
val result = runOnBackground {
124+
drca.getRegistrationState(
125+
DeviceRegistrationRecord("tenant", "upn", "device", false, false),
126+
UUID.randomUUID()
127+
)
128+
}
129+
130+
assertEquals(DeviceState.DEVICE_VALID, result)
131+
}
132+
133+
@Test
134+
fun getRegistrationState_unknownState_returnsUnknown() {
135+
val response = GetRegistrationStateV0Response(UUID.randomUUID(), "SOME_NEW_STATE")
136+
val drca = createDrca(successStrategy(packer.pack(response)))
137+
138+
val result = runOnBackground {
139+
drca.getRegistrationState(
140+
DeviceRegistrationRecord("tenant", "upn", "device", false, false),
141+
UUID.randomUUID()
142+
)
143+
}
144+
145+
assertEquals(DeviceState.UNKNOWN, result)
146+
}
147+
148+
@Test
149+
fun getDeviceRegistrationRecord_returnsRecord() {
150+
val record = DeviceRegistrationRecord("test-tenant", "user@test.com", "device-123", false, true)
151+
val response = GetDeviceRegistrationRecordV0Response(UUID.randomUUID(), record)
152+
val drca = createDrca(successStrategy(packer.pack(response)))
153+
154+
val result = runOnBackground {
155+
drca.getDeviceRegistrationRecord("test-tenant", UUID.randomUUID())
156+
}
157+
158+
assertNotNull(result)
159+
assertEquals("test-tenant", result!!.tenantId)
160+
assertEquals("user@test.com", result.upn)
161+
assertEquals("device-123", result.deviceId)
162+
}
163+
164+
@Test
165+
fun getDeviceRegistrationRecord_notFound_returnsNull() {
166+
val response = GetDeviceRegistrationRecordV0Response(UUID.randomUUID(), null)
167+
val drca = createDrca(successStrategy(packer.pack(response)))
168+
169+
val result = runOnBackground {
170+
drca.getDeviceRegistrationRecord("unknown-tenant", UUID.randomUUID())
171+
}
172+
173+
assertNull(result)
174+
}
175+
176+
@Test
177+
fun getAllEntries_returnsList() {
178+
val records = listOf<IDeviceRegistrationRecord>(
179+
DeviceRegistrationRecord("tenant1", "upn1", "dev1", false, false),
180+
DeviceRegistrationRecord("tenant2", "upn2", "dev2", true, false)
181+
)
182+
val response = GetDeviceRegistrationRecordsV0Response(UUID.randomUUID(), records)
183+
val drca = createDrca(successStrategy(packer.pack(response)))
184+
185+
val result = runOnBackground {
186+
drca.getAllEntries(UUID.randomUUID())
187+
}
188+
189+
assertEquals(2, result.size)
190+
assertEquals("tenant1", result[0].tenantId)
191+
assertEquals("tenant2", result[1].tenantId)
192+
}
193+
194+
@Test
195+
fun correlationId_isPassedToParameters() {
196+
val correlationId = UUID.randomUUID()
197+
val response = PreProvisionedBlobV0Response(UUID.randomUUID(), "jws")
198+
val strategy: IIpcStrategy = mock()
199+
whenever(strategy.getType()).thenReturn(IIpcStrategy.Type.CONTENT_PROVIDER)
200+
whenever(strategy.communicateToBroker(any())).thenAnswer { invocation ->
201+
val bundle = (invocation.arguments[0] as com.microsoft.identity.common.internal.broker.ipc.BrokerOperationBundle).bundle
202+
val protocolData = bundle?.getByteArray("protocol.data")
203+
assertNotNull(protocolData)
204+
// Return the packed response
205+
packer.pack(response)
206+
}
207+
208+
val drca = createDrca(strategy)
209+
210+
runOnBackground {
211+
drca.getPreProvisionedBlob("test-tenant", correlationId)
212+
}
213+
// If we get here without exception, the correlationId was accepted and the flow completed
214+
}
215+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.deviceregistration.java
24+
25+
/**
26+
* Represents the WPJ device state as queried from ADRS.
27+
*/
28+
enum class DeviceState {
29+
/** Device is found and valid. */
30+
DEVICE_VALID,
31+
/** Device was not found. */
32+
DEVICE_NOT_FOUND,
33+
/** Device was disabled (e.g. by the admin). */
34+
DEVICE_DISABLED,
35+
/** Unexpected state. */
36+
UNKNOWN;
37+
38+
companion object {
39+
/**
40+
* Converts a raw state string (from V0 protocol response) to a [DeviceState].
41+
* Returns [UNKNOWN] if the string doesn't match any known state.
42+
*/
43+
@JvmStatic
44+
fun fromString(state: String): DeviceState {
45+
return try {
46+
valueOf(state)
47+
} catch (e: IllegalArgumentException) {
48+
UNKNOWN
49+
}
50+
}
51+
}
52+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.deviceregistration.java
24+
25+
/**
26+
* Discovery endpoint flags to indicate the instance for device registration.
27+
*/
28+
enum class DrsDiscoveryEndpoint {
29+
/** Production instance. */
30+
PROD,
31+
/** PPE instance. */
32+
PPE,
33+
/** Int instance. */
34+
INT;
35+
36+
companion object {
37+
/**
38+
* Converts a string into [DrsDiscoveryEndpoint].
39+
* Returns [PROD] by default if not recognizable.
40+
*/
41+
@JvmStatic
42+
fun fromString(value: String?): DrsDiscoveryEndpoint {
43+
if (value.isNullOrEmpty()) return PROD
44+
return try {
45+
valueOf(value)
46+
} catch (e: IllegalArgumentException) {
47+
PROD
48+
}
49+
}
50+
}
51+
}

common4j/src/main/com/microsoft/identity/deviceregistration/java/protocol/parameters/AbstractDeviceRegistrationProtocolParameters.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,9 @@ public abstract class AbstractDeviceRegistrationProtocolParameters implements ID
3636

3737
@Getter
3838
@NonNull
39-
protected final UUID mCorrelationId = UUID.randomUUID();
39+
protected final UUID mCorrelationId;
40+
41+
protected AbstractDeviceRegistrationProtocolParameters(@NonNull final UUID correlationId) {
42+
mCorrelationId = correlationId;
43+
}
4044
}

0 commit comments

Comments
 (0)