Skip to content

Commit 27ef15d

Browse files
authored
Fix #862 Add "urn:scim:schemas:extension:slack:guest:1.0" schema support in the SCIM API client (#886)
* Fix #862 Add "urn:scim:schemas:extension:slack:guest:1.0" schema support in the SCIM API client * Disable Types constructor
1 parent fa458c2 commit 27ef15d

File tree

6 files changed

+313
-13
lines changed

6 files changed

+313
-13
lines changed

json-logs/samples/scim/v1/Users/00000000000.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,9 @@
6868
],
6969
"urn:scim:schemas:extension:enterprise:1.0": {
7070
"manager": {}
71+
},
72+
"urn:scim:schemas:extension:slack:guest:1.0": {
73+
"type": "",
74+
"expiration": ""
7175
}
7276
}

slack-api-client/src/main/java/com/slack/api/scim/model/Schemas.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ private Schemas() {
77

88
public static final String SCHEMA_CORE = "urn:scim:schemas:core:1.0";
99
public static final String SCHEMA_EXTENSION_ENTERPRISE = "urn:scim:schemas:extension:enterprise:1.0";
10+
public static final String SCHEMA_EXTENSION_SLACK_GUEST = "urn:scim:schemas:extension:slack:guest:1.0";
1011

1112
}

slack-api-client/src/main/java/com/slack/api/scim/model/User.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package com.slack.api.scim.model;
22

33
import com.google.gson.annotations.SerializedName;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
46
import lombok.Data;
7+
import lombok.NoArgsConstructor;
58

69
import java.util.Arrays;
710
import java.util.List;
811

912
@Data
1013
public class User {
1114

12-
private List<String> schemas = Arrays.asList(Schemas.SCHEMA_CORE, Schemas.SCHEMA_EXTENSION_ENTERPRISE);
15+
private List<String> schemas = Arrays.asList(
16+
Schemas.SCHEMA_CORE,
17+
Schemas.SCHEMA_EXTENSION_ENTERPRISE,
18+
Schemas.SCHEMA_EXTENSION_SLACK_GUEST
19+
);
1320

1421
private String id;
1522
private String externalId;
@@ -37,30 +44,44 @@ public class User {
3744

3845
@SerializedName(Schemas.SCHEMA_EXTENSION_ENTERPRISE)
3946
private Extension extension;
47+
@SerializedName(Schemas.SCHEMA_EXTENSION_SLACK_GUEST)
48+
private SlackGuest slackGuest;
4049

4150
private List<Group> groups;
4251

4352
@Data
53+
@Builder
54+
@NoArgsConstructor
55+
@AllArgsConstructor
4456
public static class Meta {
4557
private String created;
4658
private String location;
4759
}
4860

4961
@Data
62+
@Builder
63+
@NoArgsConstructor
64+
@AllArgsConstructor
5065
public static class Name {
5166
private String givenName;
5267
private String familyName;
5368
private String honorificPrefix;
5469
}
5570

5671
@Data
72+
@Builder
73+
@NoArgsConstructor
74+
@AllArgsConstructor
5775
public static class Email {
5876
private String value;
5977
private String type;
6078
private Boolean primary;
6179
}
6280

6381
@Data
82+
@Builder
83+
@NoArgsConstructor
84+
@AllArgsConstructor
6485
public static class Address {
6586
private String streetAddress;
6687
private String locality;
@@ -71,26 +92,38 @@ public static class Address {
7192
}
7293

7394
@Data
95+
@Builder
96+
@NoArgsConstructor
97+
@AllArgsConstructor
7498
public static class PhoneNumber {
7599
private String value;
76100
private String type;
77101
private Boolean primary;
78102
}
79103

80104
@Data
105+
@Builder
106+
@NoArgsConstructor
107+
@AllArgsConstructor
81108
public static class Photo {
82109
private String value;
83110
private String type;
84111
}
85112

86113
@Data
114+
@Builder
115+
@NoArgsConstructor
116+
@AllArgsConstructor
87117
public static class Role {
88118
private String value;
89119
private String type;
90120
private Boolean primary;
91121
}
92122

93123
@Data
124+
@Builder
125+
@NoArgsConstructor
126+
@AllArgsConstructor
94127
public static class Extension {
95128
private String employeeNumber;
96129
private String costCenter;
@@ -106,9 +139,36 @@ public static class Manager {
106139
}
107140

108141
@Data
142+
@Builder
143+
@NoArgsConstructor
144+
@AllArgsConstructor
109145
public static class Group {
110146
private String value;
111147
private String display;
112148
}
113149

150+
@Data
151+
@Builder
152+
@NoArgsConstructor
153+
@AllArgsConstructor
154+
public static class SlackGuest {
155+
public static class Types {
156+
private Types() {
157+
}
158+
159+
public static final String MULTI = "multi";
160+
}
161+
162+
/**
163+
* This value is mandatory.
164+
* possible values: "multi"
165+
*/
166+
private String type;
167+
/**
168+
* This value is optional.
169+
* possible values: ISO 8601 date time string (e.g., "2020-11-30T23:59:59Z")
170+
*/
171+
private String expiration;
172+
}
173+
114174
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package test_locally.api.scim;
2+
3+
import com.slack.api.SlackConfig;
4+
import com.slack.api.scim.model.User;
5+
import com.slack.api.util.json.GsonFactory;
6+
import org.junit.Test;
7+
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertNotNull;
10+
11+
public class ModelsTest {
12+
13+
@Test
14+
public void extensions() {
15+
assertEquals(User.SlackGuest.Types.MULTI, "multi");
16+
17+
User user = new User();
18+
user.setId("W1234567890");
19+
User.Extension enterprise = new User.Extension();
20+
enterprise.setDivision("Engineering");
21+
user.setExtension(User.Extension.builder()
22+
.employeeNumber("123-456")
23+
.organization("PDE")
24+
.division("Product")
25+
.department("Product")
26+
.build());
27+
user.setSlackGuest(User.SlackGuest.builder()
28+
.type("multi")
29+
.expiration("2020-11-30T23:59:59Z")
30+
.build());
31+
32+
assertNotNull(user);
33+
assertNotNull(user.getExtension().getDivision());
34+
assertNotNull(user.getSlackGuest().getType());
35+
36+
String json = GsonFactory.createCamelCase(SlackConfig.DEFAULT).toJson(user);
37+
String expectedJson = "{\"schemas\":[\"urn:scim:schemas:core:1.0\",\"urn:scim:schemas:extension:enterprise:1.0\",\"urn:scim:schemas:extension:slack:guest:1.0\"],\"id\":\"W1234567890\",\"urn:scim:schemas:extension:enterprise:1.0\":{\"employeeNumber\":\"123-456\",\"organization\":\"PDE\",\"division\":\"Product\",\"department\":\"Product\"},\"urn:scim:schemas:extension:slack:guest:1.0\":{\"type\":\"multi\",\"expiration\":\"2020-11-30T23:59:59Z\"}}";
38+
assertEquals(expectedJson, json);
39+
}
40+
}

slack-api-client/src/test/java/test_with_remote_apis/scim/ApiTest.java

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
import org.junit.Test;
1313

1414
import java.io.IOException;
15+
import java.text.DateFormat;
16+
import java.text.SimpleDateFormat;
17+
import java.time.ZonedDateTime;
1518
import java.util.Arrays;
19+
import java.util.Date;
1620

1721
import static org.hamcrest.CoreMatchers.*;
1822
import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,7 +34,15 @@ public static void tearDown() throws InterruptedException {
3034

3135
String orgAdminToken = System.getenv(Constants.SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN);
3236

33-
private UsersCreateResponse createNewUser() throws IOException, SCIMApiException {
37+
private UsersCreateResponse createNewFullUser() throws IOException, SCIMApiException {
38+
return createNewUser(true);
39+
}
40+
41+
private UsersCreateResponse createNewGuestUser() throws IOException, SCIMApiException {
42+
return createNewUser(false);
43+
}
44+
45+
private UsersCreateResponse createNewUser(boolean isFullMember) throws IOException, SCIMApiException {
3446
String userName = "user" + System.currentTimeMillis();
3547
User newUser = new User();
3648
newUser.setName(new User.Name());
@@ -40,6 +52,15 @@ private UsersCreateResponse createNewUser() throws IOException, SCIMApiException
4052
User.Email email = new User.Email();
4153
email.setValue("seratch+" + System.currentTimeMillis() + "@gmail.com");
4254
newUser.setEmails(Arrays.asList(email));
55+
if (!isFullMember) {
56+
// guest
57+
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
58+
String expiration = df.format(Date.from(ZonedDateTime.now().plusDays(14).toInstant()));
59+
newUser.setSlackGuest(User.SlackGuest.builder()
60+
.type(User.SlackGuest.Types.MULTI)
61+
.expiration(expiration)
62+
.build());
63+
}
4364
return slack.scim(orgAdminToken).createUser(req -> req.user(newUser));
4465
}
4566

@@ -101,9 +122,9 @@ public void readUser_dummy() throws IOException {
101122
}
102123

103124
@Test
104-
public void createAndDeleteUser() throws Exception {
125+
public void createAndDeleteFullUser() throws Exception {
105126
if (orgAdminToken != null) {
106-
UsersCreateResponse creation = createNewUser();
127+
UsersCreateResponse creation = createNewFullUser();
107128
assertThat(creation.getId(), is(notNullValue()));
108129

109130
UsersReadResponse readResp = slack.scim(orgAdminToken).readUser(req -> req.id(creation.getId()));
@@ -165,6 +186,78 @@ public void createAndDeleteUser() throws Exception {
165186
}
166187
}
167188

189+
@Test
190+
public void createAndDeleteGuestUser() throws Exception {
191+
if (orgAdminToken != null) {
192+
UsersCreateResponse creation = createNewGuestUser();
193+
assertThat(creation.getId(), is(notNullValue()));
194+
195+
UsersReadResponse readResp = slack.scim(orgAdminToken).readUser(req -> req.id(creation.getId()));
196+
assertThat(readResp.getId(), is(creation.getId()));
197+
assertThat(readResp.getUserName(), is(creation.getUserName()));
198+
assertThat(readResp.getSlackGuest().getType(), is(User.SlackGuest.Types.MULTI));
199+
assertThat(readResp.getSlackGuest().getExpiration(), is(notNullValue()));
200+
201+
try {
202+
// https://api.slack.com/scim#filter
203+
// filter query matching the created data
204+
String userName = creation.getUserName();
205+
UsersSearchResponse searchResp = slack.scim(orgAdminToken).searchUsers(req -> req.count(1).filter("userName eq \"" + userName + "\""));
206+
assertThat(searchResp.getItemsPerPage(), is(1));
207+
assertThat(searchResp.getResources().size(), is(1));
208+
assertThat(searchResp.getResources().get(0).getId(), is(creation.getId()));
209+
assertThat(searchResp.getResources().get(0).getUserName(), is(creation.getUserName()));
210+
assertThat(searchResp.getResources().get(0).getSlackGuest(), is(notNullValue()));
211+
212+
String originalUserName = creation.getUserName();
213+
214+
User user = new User();
215+
user.setUserName(originalUserName + "_ed");
216+
slack.scim(orgAdminToken).patchUser(req -> req.id(creation.getId()).user(user));
217+
218+
readResp = slack.scim(orgAdminToken).readUser(req -> req.id(creation.getId()));
219+
assertThat(readResp.getId(), is(creation.getId()));
220+
assertThat(readResp.getUserName(), is(originalUserName + "_ed"));
221+
222+
User modifiedUser = readResp;
223+
modifiedUser.setUserName(originalUserName + "_rv");
224+
modifiedUser.setNickName(originalUserName + "_rv"); // required
225+
slack.scim(orgAdminToken).updateUser(req -> req.id(modifiedUser.getId()).user(modifiedUser));
226+
227+
readResp = slack.scim(orgAdminToken).readUser(req -> req.id(modifiedUser.getId()));
228+
assertThat(readResp.getId(), is(creation.getId()));
229+
assertThat(readResp.getUserName(), is(originalUserName + "_rv"));
230+
assertThat(readResp.getSlackGuest().getType(), is(User.SlackGuest.Types.MULTI));
231+
assertThat(readResp.getSlackGuest().getExpiration(), is(notNullValue()));
232+
233+
} finally {
234+
UsersDeleteResponse deletion = slack.scim(orgAdminToken).deleteUser(req -> req.id(creation.getId()));
235+
assertThat(deletion, is(nullValue()));
236+
237+
// can call twice
238+
UsersDeleteResponse deletion2 = slack.scim(orgAdminToken).deleteUser(req -> req.id(creation.getId()));
239+
assertThat(deletion2, is(nullValue()));
240+
241+
// Verify if the deletion is completed (there is some delay)
242+
int counter = 0;
243+
UsersReadResponse supposedToBeDeleted = null;
244+
while (counter < 10) {
245+
Thread.sleep(1000);
246+
supposedToBeDeleted = slack.scim(orgAdminToken).readUser(req -> req.id(creation.getId()));
247+
if (!supposedToBeDeleted.getActive()) {
248+
break;
249+
}
250+
}
251+
assertThat(supposedToBeDeleted, is(notNullValue()));
252+
assertThat(supposedToBeDeleted.getActive(), is(false));
253+
254+
// can call again
255+
UsersDeleteResponse deletion3 = slack.scim(orgAdminToken).deleteUser(req -> req.id(creation.getId()));
256+
assertThat(deletion3, is(nullValue()));
257+
}
258+
}
259+
}
260+
168261
@Test
169262
public void groups() throws IOException, SCIMApiException {
170263
if (orgAdminToken != null) {
@@ -174,7 +267,7 @@ public void groups() throws IOException, SCIMApiException {
174267
newGroup.setDisplayName("Test Group" + System.currentTimeMillis());
175268

176269
Group.Member member = new Group.Member();
177-
UsersCreateResponse newUser = createNewUser();
270+
UsersCreateResponse newUser = createNewFullUser();
178271
assertThat(newUser.getId(), is(notNullValue()));
179272
member.setValue(newUser.getId());
180273
newGroup.setMembers(Arrays.asList(member));
@@ -210,7 +303,7 @@ public void groupAndUsers() throws IOException, SCIMApiException {
210303
if (orgAdminToken != null) {
211304
Group newGroup = new Group();
212305
newGroup.setDisplayName("Test Group" + System.currentTimeMillis());
213-
UsersCreateResponse newUser = createNewUser();
306+
UsersCreateResponse newUser = createNewFullUser();
214307
assertThat(newUser.getId(), is(notNullValue()));
215308
try {
216309
Group.Member member = new Group.Member();

0 commit comments

Comments
 (0)