Skip to content

Commit ae4387c

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 ae4387c

22 files changed

Lines changed: 876 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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@
2727
import androidx.annotation.NonNull;
2828

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

3134
/**
3235
* Interface for the callback
33-
* {@link DeviceRegistrationClientApplication#installCert(IDeviceRegistrationRecord, Activity, InstallCertCallback)}.
36+
* {@link com.microsoft.identity.deviceregistration.DeviceRegistrationClientApplication#installCert(IDeviceRegistrationRecord, Activity, IInstallCertCallback, UUID)}
3437
* If certificate is installed successfully, it will be returned through onSuccess.
3538
* Otherwise, a {@link BaseException} will be returned through onError.
3639
*/

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

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