Skip to content

Commit 4d52f55

Browse files
creechyxx-dbrameshchandra
authored
Add a user-database and constrain access to known users. (unitycatalog#387)
**PR Checklist** - [X] A description of the changes is added to the description of this PR. - [x] If there is a related issue, make sure it is linked to this PR. - [x] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added or modified a feature, documentation in `docs` is updated **Description of changes** This enhancement adds a user database to the platform, a control API for user management, CLI commands to manage users, and when authentication is enabled, will constrain access to only the users in the user database. Related Issue unitycatalog#229 The user record is simple for this first implementation - id - unique identifier for the user - name - The user's full name - email - The user's primary email address - picture_url - A link to an avatar for the user - external_id - An identifier for external identity providers to track the user - created_at - the time the record was created - updated_at - the last time the record was updated There is the beginning of a Control API which is meant to house operations that are outside of the core of the Unity Catalog specification. The Control API initially provides endpoints to do user management. The endpoints are SCIM 2.0 compatible, as such they should allow integration with third party identity providers for syncing users between systems. The CLI enhancements add commands to add, update and remove users, see command line help for details, but as an example, to add a user ``` uc user create --name "Iggy Pop" --email [email protected] ``` Finally, when the recently added OAuth authorization functionality is enabled, users must exist in the user database in order to gain access to the server operations. --------- Signed-off-by: Mocker <[email protected]> Co-authored-by: Xiang Xu <[email protected]> Co-authored-by: Ramesh Chandra <[email protected]>
1 parent a0ef1bb commit 4d52f55

File tree

20 files changed

+1303
-13
lines changed

20 files changed

+1303
-13
lines changed

api/control.yaml

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
openapi: 3.0.0
2+
servers:
3+
- url: http://localhost:8080/api/1.0/unity-control
4+
description: Localhost reference server
5+
tags:
6+
- name: Users
7+
description: |
8+
A user is the identity recognized by Unity Catalog.
9+
10+
paths:
11+
/scim2/Users:
12+
post:
13+
tags:
14+
- Users
15+
operationId: createUser
16+
summary: Create a user
17+
description: |
18+
Creates a new user.
19+
requestBody:
20+
content:
21+
application/json:
22+
schema:
23+
$ref: '#/components/schemas/UserResource'
24+
responses:
25+
'200':
26+
description: The new user was successfully created.
27+
content:
28+
application/scim+json:
29+
schema:
30+
$ref: '#/components/schemas/UserResource'
31+
get:
32+
tags:
33+
- Users
34+
parameters:
35+
- name: filter
36+
in: query
37+
description: Query by which the results have to be filtered.
38+
schema:
39+
type: string
40+
required: false
41+
- name: startIndex
42+
in: query
43+
description: Specifies the index of the first result. First item is number 1.
44+
schema:
45+
type: integer
46+
format: int32
47+
required: false
48+
- name: count
49+
in: query
50+
description: Desired number of results per page. If no count is provided, it defaults to 50.
51+
schema:
52+
type: integer
53+
format: int32
54+
required: false
55+
operationId: listUsers
56+
summary: List users
57+
description: |
58+
Gets details for all the users.
59+
responses:
60+
'200':
61+
description: The user list was successfully retrieved.
62+
content:
63+
application/scim+json:
64+
schema:
65+
$ref: '#/components/schemas/UserResourceList'
66+
/scim2/Users/{id}:
67+
parameters:
68+
- name: id
69+
in: path
70+
description: The id of the user.
71+
required: true
72+
schema:
73+
type: string
74+
get:
75+
tags:
76+
- Users
77+
operationId: getUser
78+
summary: Get a user
79+
description: |
80+
Gets the specified user.
81+
responses:
82+
'200':
83+
description: The user was successfully retrieved.
84+
content:
85+
application/scim+json:
86+
schema:
87+
$ref: '#/components/schemas/UserResource'
88+
put:
89+
tags:
90+
- Users
91+
operationId: updateUser
92+
summary: Update a user
93+
description: |
94+
Updates the user that matches the supplied id.
95+
requestBody:
96+
content:
97+
application/json:
98+
schema:
99+
$ref: '#/components/schemas/UserResource'
100+
responses:
101+
'200':
102+
description: The user was successfully updated.
103+
content:
104+
application/scim+json:
105+
schema:
106+
$ref: '#/components/schemas/UserResource'
107+
delete:
108+
tags:
109+
- Users
110+
operationId: deleteUser
111+
summary: Delete a user
112+
description: |
113+
Deletes the user that matches the supplied id.
114+
responses:
115+
'200':
116+
description: The user was successfully deleted.
117+
content:
118+
application/scim+json:
119+
schema: {}
120+
/scim2/Users/self:
121+
get:
122+
tags:
123+
- Users
124+
operationId: getSelf
125+
summary: Get the current user
126+
description: |
127+
Gets the user from the jwt token provided.
128+
responses:
129+
'200':
130+
description: The user was successfully retrieved.
131+
content:
132+
application/json:
133+
schema:
134+
$ref: '#/components/schemas/UserResource'
135+
136+
components:
137+
schemas:
138+
User:
139+
properties:
140+
id:
141+
description: The unique identifier of the user.
142+
type: string
143+
name:
144+
description: The name of the user.
145+
type: string
146+
email:
147+
description: The email address of the user.
148+
type: string
149+
external_id:
150+
description: The external identifier of the user.
151+
type: string
152+
state:
153+
description: The state of the account.
154+
type: string
155+
enum:
156+
- ENABLED
157+
- DISABLED
158+
picture_url:
159+
description: The URL of the user's profile picture.
160+
type: string
161+
created_at:
162+
description: The time the user was created.
163+
type: integer
164+
format: int64
165+
updated_at:
166+
description: The time the user was last updated.
167+
type: integer
168+
format: int64
169+
type: object
170+
required:
171+
- id
172+
- name
173+
- email
174+
UserResourceList:
175+
description: List of SCIM User resources.
176+
type: object
177+
properties:
178+
totalResults:
179+
description: The total number of results.
180+
type: integer
181+
format: int32
182+
itemsPerPage:
183+
description: The number of items per page.
184+
type: integer
185+
format: int32
186+
startIndex:
187+
description: The index of the first result.
188+
type: integer
189+
format: int32
190+
Resources:
191+
description: The list of User resources.
192+
type: array
193+
items:
194+
$ref: '#/components/schemas/UserResource'
195+
id:
196+
description: User list id metadata.
197+
type: string
198+
externalId:
199+
description: User list external id metadata.
200+
type: string
201+
meta:
202+
description: The metadata of the user.
203+
type: object
204+
properties:
205+
resourceType:
206+
type: string
207+
created:
208+
type: string
209+
lastModified:
210+
type: string
211+
UserResource:
212+
description: SCIM provides a resource type for "User" resources.
213+
type: object
214+
properties:
215+
id:
216+
description: The id of the user.
217+
type: string
218+
displayName:
219+
description: The name of the user.
220+
type: string
221+
externalId:
222+
description: The SCIM external id.
223+
type: string
224+
emails:
225+
description: E-mail addresses for the user.
226+
type: array
227+
items:
228+
$ref: '#/components/schemas/Email'
229+
active:
230+
description: The active status of the user.
231+
type: boolean
232+
meta:
233+
description: The metadata of the user.
234+
type: object
235+
properties:
236+
created:
237+
type: string
238+
lastModified:
239+
type: string
240+
photos:
241+
description: The photos of the user.
242+
type: array
243+
items:
244+
type: object
245+
properties:
246+
value:
247+
description: The url of the user's photo.
248+
type: string
249+
Email:
250+
description: SCIM email for a user.
251+
type: object
252+
properties:
253+
value:
254+
description: The email of the user.
255+
type: string
256+
primary:
257+
description: If the email is primary.
258+
type: boolean
259+
info:
260+
title: Unity Control API
261+
version: '0.1'

build.sbt

+87-2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ lazy val commonSettings = Seq(
7979
// https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
8080
// We can use and distribute the, but not modify the source code
8181
case DepModuleInfo("org.hibernate.orm", _, _) => true
82+
case DepModuleInfo("com.unboundid.scim2", _, _) => true
83+
case DepModuleInfo("com.unboundid.product.scim2", _, _) => true
8284
// Duo license:
8385
// - Eclipse Public License 2.0
8486
// - GNU General Public License, version 2 with the GNU Classpath Exception
@@ -112,9 +114,52 @@ def javafmtCheckSettings() = Seq(
112114
(Compile / compile) := ((Compile / compile) dependsOn (Compile / javafmtCheckAll)).value
113115
)
114116

117+
lazy val controlApi = (project in file("target/control/java"))
118+
.enablePlugins(OpenApiGeneratorPlugin)
119+
.disablePlugins(JavaFormatterPlugin)
120+
.settings(
121+
name := s"$artifactNamePrefix-controlapi",
122+
commonSettings,
123+
javaOnlyReleaseSettings,
124+
libraryDependencies ++= Seq(
125+
"jakarta.annotation" % "jakarta.annotation-api" % "3.0.0" % Provided,
126+
"com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion,
127+
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
128+
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % jacksonVersion,
129+
),
130+
(Compile / compile) := ((Compile / compile) dependsOn generate).value,
131+
132+
// OpenAPI generation specs
133+
openApiInputSpec := (file(".") / "api" / "control.yaml").toString,
134+
openApiGeneratorName := "java",
135+
openApiOutputDir := (file("target") / "control" / "java").toString,
136+
openApiApiPackage := s"$orgName.control.api",
137+
openApiModelPackage := s"$orgName.control.model",
138+
openApiAdditionalProperties := Map(
139+
"library" -> "native",
140+
"useJakartaEe" -> "true",
141+
"hideGenerationTimestamp" -> "true",
142+
"openApiNullable" -> "false"),
143+
openApiGenerateApiTests := SettingDisabled,
144+
openApiGenerateModelTests := SettingDisabled,
145+
openApiGenerateApiDocumentation := SettingDisabled,
146+
openApiGenerateModelDocumentation := SettingDisabled,
147+
// Define the simple generate command to generate full client codes
148+
generate := {
149+
val _ = openApiGenerate.value
150+
151+
// Delete the generated build.sbt file so that it is not used for our sbt config
152+
val buildSbtFile = file(openApiOutputDir.value) / "build.sbt"
153+
if (buildSbtFile.exists()) {
154+
buildSbtFile.delete()
155+
}
156+
}
157+
)
158+
115159
lazy val client = (project in file("target/clients/java"))
116160
.enablePlugins(OpenApiGeneratorPlugin)
117161
.disablePlugins(JavaFormatterPlugin)
162+
.dependsOn(controlApi % "compile->compile")
118163
.settings(
119164
name := s"$artifactNamePrefix-client",
120165
commonSettings,
@@ -206,8 +251,6 @@ lazy val server = (project in file("server"))
206251
"com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
207252
"com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % jacksonVersion,
208253
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % jacksonVersion,
209-
"com.auth0" % "java-jwt" % "4.4.0",
210-
"com.auth0" % "jwks-rsa" % "0.22.1",
211254

212255
"com.google.code.findbugs" % "jsr305" % "3.0.2",
213256
"com.h2database" % "h2" % "2.2.224",
@@ -242,6 +285,12 @@ lazy val server = (project in file("server"))
242285
"io.vertx" % "vertx-web" % "4.3.5",
243286
"io.vertx" % "vertx-web-client" % "4.3.5",
244287

288+
// Auth dependencies
289+
"com.unboundid.product.scim2" % "scim2-sdk-common" % "3.1.0",
290+
"org.springframework" % "spring-expression" % "6.1.11",
291+
"com.auth0" % "java-jwt" % "4.4.0",
292+
"com.auth0" % "jwks-rsa" % "0.22.1",
293+
245294
// Test dependencies
246295
"org.junit.jupiter" % "junit-jupiter" % "5.10.3" % Test,
247296
"org.mockito" % "mockito-core" % "5.11.0" % Test,
@@ -278,6 +327,7 @@ lazy val server = (project in file("server"))
278327
lazy val serverModels = (project in file("server") / "target" / "models")
279328
.enablePlugins(OpenApiGeneratorPlugin)
280329
.disablePlugins(JavaFormatterPlugin)
330+
.dependsOn(controlModels % "compile->compile")
281331
.settings(
282332
name := s"$artifactNamePrefix-servermodels",
283333
commonSettings,
@@ -310,6 +360,41 @@ lazy val serverModels = (project in file("server") / "target" / "models")
310360
}
311361
)
312362

363+
lazy val controlModels = (project in file("server") / "target" / "controlmodels")
364+
.enablePlugins(OpenApiGeneratorPlugin)
365+
.disablePlugins(JavaFormatterPlugin)
366+
.settings(
367+
name := s"$artifactNamePrefix-controlmodels",
368+
commonSettings,
369+
(Compile / compile) := ((Compile / compile) dependsOn generate).value,
370+
Compile / compile / javacOptions ++= javacRelease17,
371+
libraryDependencies ++= Seq(
372+
"jakarta.annotation" % "jakarta.annotation-api" % "3.0.0" % Provided,
373+
"com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion,
374+
),
375+
// OpenAPI generation configs for generating model codes from the spec
376+
openApiInputSpec := (file(".") / "api" / "control.yaml").toString,
377+
openApiGeneratorName := "java",
378+
openApiOutputDir := (file("server") / "target" / "controlmodels").toString,
379+
openApiValidateSpec := SettingEnabled,
380+
openApiGenerateMetadata := SettingDisabled,
381+
openApiModelPackage := s"$orgName.control.model",
382+
openApiAdditionalProperties := Map(
383+
"library" -> "resteasy", // resteasy generates the most minimal models
384+
"useJakartaEe" -> "true",
385+
"hideGenerationTimestamp" -> "true"
386+
),
387+
openApiGlobalProperties := Map("models" -> ""),
388+
openApiGenerateApiTests := SettingDisabled,
389+
openApiGenerateModelTests := SettingDisabled,
390+
openApiGenerateApiDocumentation := SettingDisabled,
391+
openApiGenerateModelDocumentation := SettingDisabled,
392+
// Define the simple generate command to generate model codes
393+
generate := {
394+
val _ = openApiGenerate.value
395+
}
396+
)
397+
313398
lazy val cli = (project in file("examples") / "cli")
314399
.dependsOn(server % "test->test")
315400
.dependsOn(client % "compile->compile;test->test")

0 commit comments

Comments
 (0)