-
Notifications
You must be signed in to change notification settings - Fork 8
/
CredentialsClientService.kt
215 lines (193 loc) · 10.6 KB
/
CredentialsClientService.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package com.izivia.ocpi.toolkit.modules.credentials.services
import com.izivia.ocpi.toolkit.common.*
import com.izivia.ocpi.toolkit.modules.credentials.CredentialsClient
import com.izivia.ocpi.toolkit.modules.credentials.domain.Credentials
import com.izivia.ocpi.toolkit.modules.credentials.repositories.CredentialsRoleRepository
import com.izivia.ocpi.toolkit.modules.credentials.repositories.PlatformRepository
import com.izivia.ocpi.toolkit.modules.versions.VersionDetailsClient
import com.izivia.ocpi.toolkit.modules.versions.VersionsClient
import com.izivia.ocpi.toolkit.modules.versions.domain.Endpoint
import com.izivia.ocpi.toolkit.modules.versions.domain.ModuleID
import com.izivia.ocpi.toolkit.modules.versions.domain.parseVersionNumber
import com.izivia.ocpi.toolkit.modules.versions.repositories.VersionsRepository
import com.izivia.ocpi.toolkit.transport.TransportClientBuilder
/**
* Automates authentification process
*
* Note: credentialsClient & versionsClient must target the same platform since the automated process will check the
* versions of the receiver before performing registration.
*
* - Client: the one doing the registration process
* - Server: the one receiving the registration process
*
* @property clientVersionsEndpointUrl the versions endpoints url of the client (for the server to use)
* @property clientPlatformRepository client's repository to store and retrieve tokens and information about platforms
* @property clientVersionsRepository client's repository to retrieve available versions on the client
* @property clientCredentialsRoleRepository client's repository to retrieve its role
* @property serverVersionsEndpointUrl the versions endpoint url of the server (for the client to retrieve endpoints)
* @property transportClientBuilder used to build a transport (will be used to create CredentialClient to make calls)
*/
class CredentialsClientService(
private val clientVersionsEndpointUrl: String,
private val clientPlatformRepository: PlatformRepository,
private val clientVersionsRepository: VersionsRepository,
private val clientCredentialsRoleRepository: CredentialsRoleRepository,
private val serverVersionsEndpointUrl: String,
private val transportClientBuilder: TransportClientBuilder
) {
suspend fun get(): Credentials = clientPlatformRepository
.getCredentialsTokenC(platformUrl = serverVersionsEndpointUrl)
?.let { tokenC ->
buildCredentialClient()
.get(tokenC = tokenC)
.let { it.data ?: throw OcpiResponseException(it.status_code, it.status_message ?: "unknown") }
}
?: throw OcpiClientGenericException("Could not find CREDENTIALS_TOKEN_C associated with platform $serverVersionsEndpointUrl")
/**
* To start using OCPI, the Platforms will need to exchange credentials tokens.
*
* To start the exchange of credentials tokens, one platform has to be selected as Sender for the Credentials
* module. This has to be decided between the Platforms (outside of OCPI) before they first connect.
*
* To start the credentials exchange, the Receiver Platform must create a unique credentials token:
* CREDENTIALS_TOKEN_A that has to be used to authorize the Sender until the credentials exchange is finished. This
* credentials token along with the versions endpoint SHOULD be sent to the Sender in a secure way that is outside
* the scope of this protocol.
*
* The Sender starts the registration process, retrieves the version information and details (using
* CREDENTIALS_TOKEN_A in the HTTP Authorization header). The Sender generates a unique credentials token:
* CREDENTIALS_TOKEN_B, sends it to the Receiver in a POST request to the credentials module of the Receiver. The
* Receiver stores CREDENTIALS_TOKEN_B and uses it for any requests to the Sender Platform, including the version
* information and details.
*
* The Receiver generates a unique credentials token: CREDENTIALS_TOKEN_C and returns it to the Sender in the
* response to the POST request from the Sender.
*
* After the credentials exchange has finished, the Sender SHALL use CREDENTIALS_TOKEN_C in future OCPI request to
* the Receiver Platform. The CREDENTIALS_TOKEN_A can then be thrown away, it MAY no longer be used.
*
* @return the credentials to use when communicating with the server (receiver)
*/
suspend fun register(): Credentials {
// Get token provided by receiver outside the OCPI protocol (for example by an admin)
val credentialsTokenA = clientPlatformRepository.getCredentialsTokenA(platformUrl = serverVersionsEndpointUrl)
?: throw OcpiClientInvalidParametersException("Could not find token A associated with platform $serverVersionsEndpointUrl")
findLatestMutualVersionAndSaveInformation()
// Generate token B
val credentialsTokenB = clientPlatformRepository.saveCredentialsTokenB(
platformUrl = serverVersionsEndpointUrl,
credentialsTokenB = generateUUIDv4Token()
)
// Initiate registration process
val credentials = buildCredentialClient().post(
tokenA = credentialsTokenA,
credentials = Credentials(
token = credentialsTokenB,
url = clientVersionsEndpointUrl,
roles = clientCredentialsRoleRepository.getCredentialsRoles()
),
debugHeaders = emptyMap()
).let {
it.data ?: throw OcpiResponseException(it.status_code, it.status_message ?: "unknown")
}
// Store token C
clientPlatformRepository.saveCredentialsTokenC(
platformUrl = serverVersionsEndpointUrl,
credentialsTokenC = credentials.token
)
// Remove token A and B because it is useless from now on
clientPlatformRepository.removeCredentialsTokenA(platformUrl = serverVersionsEndpointUrl)
clientPlatformRepository.removeCredentialsTokenB(platformUrl = serverVersionsEndpointUrl)
return credentials
}
suspend fun update(): Credentials {
// Token to communicate with receiver
val credentialsTokenC = clientPlatformRepository.getCredentialsTokenC(platformUrl = serverVersionsEndpointUrl)
?: throw OcpiClientInvalidParametersException("Could not find token C associated with platform $serverVersionsEndpointUrl")
findLatestMutualVersionAndSaveInformation()
// Generate token B
val credentialsTokenB = clientPlatformRepository.saveCredentialsTokenB(
platformUrl = serverVersionsEndpointUrl,
credentialsTokenB = generateUUIDv4Token()
)
// Initiate registration process
val credentials = buildCredentialClient().put(
tokenC = credentialsTokenC,
credentials = Credentials(
token = credentialsTokenB,
url = clientVersionsEndpointUrl,
roles = clientCredentialsRoleRepository.getCredentialsRoles()
),
debugHeaders = emptyMap()
).let {
it.data ?: throw OcpiResponseException(it.status_code, it.status_message ?: "unknown")
}
// Store token C
clientPlatformRepository.saveCredentialsTokenC(
platformUrl = serverVersionsEndpointUrl,
credentialsTokenC = credentials.token
)
return credentials
}
suspend fun delete() = clientPlatformRepository
.getCredentialsTokenC(platformUrl = serverVersionsEndpointUrl)
?.let { tokenC ->
buildCredentialClient()
.delete(tokenC = tokenC)
.also {
clientPlatformRepository.removeCredentialsTokenC(platformUrl = serverVersionsEndpointUrl)
}
.also {
if (it.status_code != OcpiStatus.SUCCESS.code)
throw OcpiResponseException(it.status_code, it.status_message ?: "unknown")
}
}
?: throw OcpiClientGenericException("Could not find CREDENTIALS_TOKEN_C associated with platform $serverVersionsEndpointUrl")
private suspend fun findLatestMutualVersionAndSaveInformation(): List<Endpoint> {
val availableServerVersions = VersionsClient(
transportClientBuilder = transportClientBuilder,
serverVersionsEndpointUrl = serverVersionsEndpointUrl,
platformRepository = clientPlatformRepository
)
.getVersions()
.let {
it.data ?: throw OcpiResponseException(it.status_code, it.status_message ?: "unknown")
}
val availableClientVersions = clientVersionsRepository.getVersions()
// Get available versions and pick latest mutual
val latestMutualVersion = availableServerVersions
.sortedByDescending { clientVersion -> parseVersionNumber(clientVersion.version)!!.index }
.firstOrNull { serverVersion ->
availableClientVersions
.any { clientVersion -> serverVersion.version == clientVersion.version }
}
?: throw OcpiServerUnsupportedVersionException("Could not find mutual version with platform $serverVersionsEndpointUrl")
// Store version that will be used
clientPlatformRepository.saveVersion(platformUrl = serverVersionsEndpointUrl, version = latestMutualVersion)
// Get available endpoints for a given version
val versionDetails = VersionDetailsClient(
transportClientBuilder = transportClientBuilder,
serverVersionsEndpointUrl = serverVersionsEndpointUrl,
platformRepository = clientPlatformRepository
)
.getVersionDetails()
.let {
it.data ?: throw OcpiResponseException(it.status_code, it.status_message ?: "unknown")
}
// Store version & endpoint
return clientPlatformRepository.saveEndpoints(platformUrl = serverVersionsEndpointUrl, endpoints = versionDetails.endpoints)
}
private suspend fun getOrFindEndpoints(): List<Endpoint> = clientPlatformRepository
.getEndpoints(platformUrl = serverVersionsEndpointUrl)
.takeIf { it.isNotEmpty() }
?: findLatestMutualVersionAndSaveInformation()
private suspend fun buildCredentialClient(): CredentialsClient = CredentialsClient(
transportClient = transportClientBuilder
.build(
baseUrl = getOrFindEndpoints()
.find { it.identifier == ModuleID.credentials }
?.url
?: throw OcpiServerUnsupportedVersionException("No credentials endpoint for $serverVersionsEndpointUrl")
)
)
}