The upcoming Jira Server’s user anonymization feature makes administrators lives easier, as it adds extensive in-product support for EU's right to be forgotten. At the same time, it has an immense impact on the majority of Marketplace apps and will break some of them.
Join Daniel Rauf, Software Engineer for Jira Server, to learn how to keep your app in a consistent state, explore newly added APIs allowing you to react to the anonymization and efficiently assess your implementations with end-to-end tests.
4. GDPR IN JIRA SERVER
Other products
Bitbucket and Confluence
already support anonymization
Issues in Jira
Almost every database table is
(accidentally) full of personal
details
Easier from 8.4
With new user key generation
strategy, the data is better
contained
5. GDPR IN JIRA SERVER
Other products
Bitbucket and Confluence
already support anonymization
Issues in Jira
Almost every database table is
(accidentally) full of personal
details
Easier from 8.4
With new user key generation
strategy, the data is better
contained
6. GDPR IN JIRA SERVER
Other products
Bitbucket and Confluence
already support anonymization
Issues in Jira
Almost every database table is
(accidentally) full of personal
details
Easier from 8.4
With new user key generation
strategy, the data is better
contained
36. public class WatchersAnonymizationHandler
implements UserAnonymizationHandler {
// ...
}
public class AnnouncementsAnonymizationHandler
implements OwnershipTransferHandler {
// ...
}
public class CommentsAnonymizationHandler
implements UserKeyChangeHandler {
// ...
}
40. public class ErrorHandlingRunner {
T execute(Callable<T> callable) {
// ...
}
void executeAndUpdateProgress(Runnable runnable) {
// ...
}
ServiceResult getResult() {
// ...
}
}
41. @Override
public ServiceResult update(...) {
final ErrorHandlingRunner runner =
new ErrorHandlingRunner(...);
runner.executeAndUpdateProgress(() ->
ao.deleteWithSQL(
WatcherAO.class,
WATCHER_CLAUSE,
parameter.getUserKey()
)
);
return runner.getResult();
}
42. @Override
public ServiceResult update(...) {
final ErrorHandlingRunner runner = ...;
final Stream<AnnouncementAO> announcements =
runner.execute(() -> Arrays.stream(ao.find(
AnnouncementAO.class,
getPointOfContactQuery(ownerKey)))
);
if (announcements != null) {
announcements.forEach(announcement ->
updateAnnouncement(
newOnerKey, runner, announcement
)
);
}
return runner.getResult();
}
43. @Override
public ServiceResult update(...) {
final String original = parameter.getOriginal();
final String target = parameter.getTarget();
final ErrorHandlingRunner runner = ...;
runner.execute(() -> ao.stream(
CommentAO.class,
getAuthorQuery(original),
comment -> updateComment(
target, runner, comment.getID()
));
));
return runner.getResult();
}
44. @Override
public void updateComment(...) {
runner.executeAndUpdateProgress(() -> {
final CommentAO comment = ao.get(
CommentAO.class, commentId
);
if (comment == null) {
// log a warning and return
}
comment.setAuthor(newAuthorKey);
comment.save();
});
}
45. // watchers
public int getNumberOfTasks(...) {
return 1;
}
// announcements
public int getNumberOfTasks(...) {
return getNumberOfAnnouncements(...);
}
// comments
public int getNumberOfTasks(...) {
return getNumberOfComments(...);
}
46. // inside the backdoor plugin
public class AnonymizeBackdoor {
// uses AnonymizeUserService from jira-core
}
// inside integration-tests project
public class AnonymizeControl {
// calls the backdoor plugin via REST
}
47. // create comments using the backdoor
// ...
// anonymise the user
final AnonymizeResult result =
backdoor.anonymize().anonymizeUser(userKey);
final String newKey = result.getNewUserKey();
// assert that comments got updated
assertThat(
backdoor.comments().getCommentsByAuthor(userKey).size,
equalTo(0)
);
assertThat(
backdoor.comments().getCommentsByAuthor(newKey),
containsInAnyOrder(comment1, comment2, ...)
);
48. // disable feature flag to create user keys like before
backdoor.darkFeatures().disableForSite(
FeatureFlag.featureFlag(
DB_ID_BASED_KEY_GENERATION_STRATEGY.featureKey()
)
);
// use users from the default data set
@RestoreBlankInstance
// ...
backdoor
.usersAndGroups()
.getUserByName("fred")
.getKey();
// use users from your own XML backup
@Restore("some_file.xml")
49. Summary
Fill in the implementation
Always consider performance in
getAffectedEntities, and memory
usage in update method
Make sure it works
Create integration tests to make sure
everything works fine
Choose handler type(s)
Decide which handlers, if any, you
need to implement
Register them
Tip: start by throwing exceptions from
every method and see if they appear
in the UI
50. DANIEL RAUF | SOFTWARE ENGINEER | ATLASSIAN
Thank you!