Skip to content

Commit 4539cfa

Browse files
mohitc1Copilot
andauthored
Phase 2 - Add DeviceRegistrationClientApplication in Common For OneAuth, Fixes AB#3450073 (#3073)
## Summary Introduce `DeviceRegistrationClientApplication` as a public API for OneAuth consumers to perform device registration operations via IPC. Add `DeviceState` and `DiscoveryEndpoint` enums in common4j. ## Changes ### common4j - `DeviceState` enum — `DEVICE_VALID`, `DEVICE_NOT_FOUND`, `DEVICE_DISABLED`, `UNKNOWN` with `fromString()` factory - `DiscoveryEndpoint` enum — `PROD`, `PPE`, `INT` with `fromString()` factory - `AbstractDeviceRegistrationProtocolParameters` — removed no-arg constructor, added `(UUID correlationId)` constructor. All V0 param subclasses updated with explicit `(correlationId, ...fields)` constructors, `@AllArgsConstructor` removed. - `TestHappyPathV0Parameters` (testFixtures) — updated constructor for correlationId ### common (Android) - `DeviceRegistrationClientApplication` — new public API with two constructors: - Simple: `(Context)` — creates defaults for OneAuth consumers - Full: `(Context, IPlatformComponents, IBrokerDiscoveryClient, DeviceRegistrationIpcStrategiesProvider)` — for broker/testing - All methods require mandatory `correlationId: UUID` for IPC request tracing - `correlationId` passed to V0 protocol parameters via constructor ### Tests - `DeviceRegistrationClientApplicationTest` (7 tests) — getPreProvisionedBlob, getRegistrationState (valid + unknown), getDeviceRegistrationRecord (found + null), getAllEntries, correlationId propagation - Updated serializer and packer tests for new constructor signatures ## Companion PR Broker: https://github.com/identity-authnz-teams/ad-accounts-for-android/pull/144 Fixes [AB#3450073](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3450073) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 20bb1be commit 4539cfa

22 files changed

Lines changed: 896 additions & 46 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 DrsDiscoveryEndpoint 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/common/internal/broker/IInstallCertCallback.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@
2727
import androidx.annotation.NonNull;
2828

2929
import com.microsoft.identity.common.java.exception.BaseException;
30+
import com.microsoft.identity.deviceregistration.api.DeviceRegistrationClientApplication;
31+
import com.microsoft.identity.deviceregistration.java.api.IDeviceRegistrationRecord;
32+
33+
import java.util.UUID;
3034

3135
/**
3236
* Interface for the callback
33-
* {@link DeviceRegistrationClientApplication#installCert(IDeviceRegistrationRecord, Activity, InstallCertCallback)}.
37+
* {@link DeviceRegistrationClientApplication#installCert(IDeviceRegistrationRecord, Activity, IInstallCertCallback, UUID)}
3438
* If certificate is installed successfully, it will be returned through onSuccess.
3539
* Otherwise, a {@link BaseException} will be returned through onError.
3640
*/

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

Lines changed: 435 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: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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.api
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.BrokerOperationBundle
31+
import com.microsoft.identity.common.internal.broker.ipc.IIpcStrategy
32+
import com.microsoft.identity.common.java.interfaces.IPlatformComponents
33+
import com.microsoft.identity.common.java.interfaces.IStorageSupplier
34+
import com.microsoft.identity.deviceregistration.AndroidDeviceRegistrationProtocolPacker
35+
import com.microsoft.identity.deviceregistration.DeviceRegistrationIpcStrategiesProvider
36+
import com.microsoft.identity.deviceregistration.java.DeviceState
37+
import com.microsoft.identity.deviceregistration.java.api.DeviceRegistrationRecord
38+
import com.microsoft.identity.deviceregistration.java.api.IDeviceRegistrationRecord
39+
import com.microsoft.identity.deviceregistration.java.protocol.parameters.PreProvisionedBlobV0Parameters
40+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetDeviceRegistrationRecordV0Response
41+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetDeviceRegistrationRecordsV0Response
42+
import com.microsoft.identity.deviceregistration.java.protocol.response.GetRegistrationStateV0Response
43+
import com.microsoft.identity.deviceregistration.java.protocol.response.PreProvisionedBlobV0Response
44+
import org.junit.Assert
45+
import org.junit.Before
46+
import org.junit.Test
47+
import org.junit.runner.RunWith
48+
import org.mockito.Mockito
49+
import org.mockito.kotlin.any
50+
import org.mockito.kotlin.mock
51+
import org.mockito.kotlin.whenever
52+
import org.robolectric.RobolectricTestRunner
53+
import java.util.UUID
54+
55+
/**
56+
* Unit tests for [DeviceRegistrationClientApplication].
57+
* Uses mock IPC strategy to verify DRCA methods call the controller
58+
* with correct V0 params and return correctly parsed responses.
59+
*/
60+
@RunWith(RobolectricTestRunner::class)
61+
class DeviceRegistrationClientApplicationTest {
62+
63+
private lateinit var context: Context
64+
private val packer = AndroidDeviceRegistrationProtocolPacker()
65+
private val testBrokerPkg = "com.microsoft.test.broker"
66+
67+
@Before
68+
fun setup() {
69+
context = ApplicationProvider.getApplicationContext()
70+
}
71+
72+
private fun createDrca(strategy: IIpcStrategy): DeviceRegistrationClientApplication {
73+
val storageSupplier: IStorageSupplier = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS)
74+
val components: IPlatformComponents = mock {
75+
whenever(it.storageSupplier).thenReturn(storageSupplier)
76+
}
77+
val discoveryClient: IBrokerDiscoveryClient = mock {
78+
whenever(it.getActiveBroker(false)).thenReturn(BrokerData(testBrokerPkg, "sig"))
79+
}
80+
val provider = object : DeviceRegistrationIpcStrategiesProvider(false) {
81+
override fun getStrategies(
82+
context: Context,
83+
components: IPlatformComponents,
84+
activeBrokerPackageName: String
85+
): MutableList<IIpcStrategy> = mutableListOf(strategy)
86+
}
87+
return DeviceRegistrationClientApplication(context, components, discoveryClient, provider)
88+
}
89+
90+
private fun successStrategy(responseBundle: Bundle): IIpcStrategy {
91+
val strategy: IIpcStrategy = mock()
92+
whenever(strategy.getType()).thenReturn(IIpcStrategy.Type.CONTENT_PROVIDER)
93+
whenever(strategy.communicateToBroker(any())).thenReturn(responseBundle)
94+
return strategy
95+
}
96+
97+
@Test
98+
fun getPreProvisionedBlob_returnsJws() {
99+
val expectedJws = "test.jws.blob"
100+
val response = PreProvisionedBlobV0Response(UUID.randomUUID(), expectedJws)
101+
val drca = createDrca(successStrategy(packer.pack(response)))
102+
103+
val result = drca.getPreProvisionedBlob("test-tenant", UUID.randomUUID())
104+
105+
106+
Assert.assertEquals(expectedJws, result)
107+
}
108+
109+
@Test
110+
fun getRegistrationState_returnsDeviceState() {
111+
val response = GetRegistrationStateV0Response(UUID.randomUUID(), "DEVICE_VALID")
112+
val drca = createDrca(successStrategy(packer.pack(response)))
113+
114+
val result = drca.getRegistrationState(
115+
DeviceRegistrationRecord("tenant", "upn", "device", false, false),
116+
UUID.randomUUID()
117+
)
118+
119+
Assert.assertEquals(DeviceState.DEVICE_VALID, result)
120+
}
121+
122+
@Test
123+
fun getRegistrationState_unknownState_returnsUnknown() {
124+
val response = GetRegistrationStateV0Response(UUID.randomUUID(), "SOME_NEW_STATE")
125+
val drca = createDrca(successStrategy(packer.pack(response)))
126+
127+
val result = drca.getRegistrationState(
128+
DeviceRegistrationRecord("tenant", "upn", "device", false, false),
129+
UUID.randomUUID()
130+
)
131+
132+
Assert.assertEquals(DeviceState.UNKNOWN, result)
133+
}
134+
135+
@Test
136+
fun getDeviceRegistrationRecord_returnsRecord() {
137+
val record =
138+
DeviceRegistrationRecord("test-tenant", "user@test.com", "device-123", false, true)
139+
val response = GetDeviceRegistrationRecordV0Response(UUID.randomUUID(), record)
140+
val drca = createDrca(successStrategy(packer.pack(response)))
141+
142+
val result = drca.getDeviceRegistrationRecord("test-tenant", UUID.randomUUID())
143+
144+
145+
Assert.assertNotNull(result)
146+
Assert.assertEquals("test-tenant", result!!.tenantId)
147+
Assert.assertEquals("user@test.com", result.upn)
148+
Assert.assertEquals("device-123", result.deviceId)
149+
}
150+
151+
@Test
152+
fun getDeviceRegistrationRecord_notFound_returnsNull() {
153+
val response = GetDeviceRegistrationRecordV0Response(UUID.randomUUID(), null)
154+
val drca = createDrca(successStrategy(packer.pack(response)))
155+
156+
val result = drca.getDeviceRegistrationRecord("unknown-tenant", UUID.randomUUID())
157+
158+
159+
Assert.assertNull(result)
160+
}
161+
162+
@Test
163+
fun getAllEntries_returnsList() {
164+
val records = listOf<IDeviceRegistrationRecord>(
165+
DeviceRegistrationRecord("tenant1", "upn1", "dev1", false, false),
166+
DeviceRegistrationRecord("tenant2", "upn2", "dev2", true, false)
167+
)
168+
val response = GetDeviceRegistrationRecordsV0Response(UUID.randomUUID(), records)
169+
val drca = createDrca(successStrategy(packer.pack(response)))
170+
171+
val result = drca.getAllEntries(UUID.randomUUID())
172+
173+
174+
Assert.assertEquals(2, result.size)
175+
Assert.assertEquals("tenant1", result[0].tenantId)
176+
Assert.assertEquals("tenant2", result[1].tenantId)
177+
}
178+
179+
@Test
180+
fun correlationId_isPassedToParameters() {
181+
val correlationId = UUID.randomUUID()
182+
val response = PreProvisionedBlobV0Response(UUID.randomUUID(), "jws")
183+
val strategy: IIpcStrategy = mock()
184+
whenever(strategy.getType()).thenReturn(IIpcStrategy.Type.CONTENT_PROVIDER)
185+
whenever(strategy.communicateToBroker(any())).thenAnswer { invocation ->
186+
val bundle = (invocation.arguments[0] as BrokerOperationBundle).bundle
187+
val protocolData = bundle?.getByteArray("protocol.data")
188+
Assert.assertNotNull(protocolData)
189+
val parameters = PreProvisionedBlobV0Parameters.create(protocolData)
190+
Assert.assertEquals(correlationId, parameters.correlationId)
191+
// Return the packed response
192+
packer.pack(response)
193+
}
194+
195+
val drca = createDrca(strategy)
196+
197+
drca.getPreProvisionedBlob("test-tenant", correlationId)
198+
// If we get here without exception, the correlationId was accepted and the flow completed
199+
}
200+
}
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)