Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
bb7a599
Port CLARIN backend data layer to DSpace 9 (migrations + entities/ser…
milanmajchrak Jun 18, 2026
8836cc6
Wire CLARIN backend Spring beans (services runtime-active)
milanmajchrak Jun 18, 2026
e8d96df
docs: update CLARIN v9 progress (tranche 1+2 CI-green, conflict strat…
milanmajchrak Jun 18, 2026
7117af3
Resolve BE review findings: defer PIDService landmine + document not-…
milanmajchrak Jun 22, 2026
85b260f
Port CLARIN BE config + entity columns (resolves review C1 partial + C2)
milanmajchrak Jun 22, 2026
7219832
Port CLARIN License REST layer to DSpace 9 (dspace-server-webapp)
milanmajchrak Jun 22, 2026
060e4fc
Port CLARIN handle/user/token/featured-service REST to DSpace 9
milanmajchrak Jun 22, 2026
09d726c
Fix CLARIN REST endpoints 404 in DSpace 9 (plural bean-name) + runtim…
milanmajchrak Jun 23, 2026
3f75e45
Document runtime endpoint sweep + verificationtokens 500 quirk (faith…
milanmajchrak Jun 23, 2026
98771fe
Fix CLARIN REST conversion crash in DSpace 9 (findOne @PreAuthorize) …
milanmajchrak Jun 23, 2026
4871683
Document findOne @PreAuthorize fix (tranche 7) + write-path validatio…
milanmajchrak Jun 23, 2026
d28db8c
Fix CI-red duplicate @PreAuthorize regression + complete handle/epic/…
milanmajchrak Jun 23, 2026
02f123f
Port CLARIN config-file/authorization/registration/import/submission-…
milanmajchrak Jun 23, 2026
33172a3
Port CLARIN vanilla-file methods: Item.isHidden + WorkspaceItem.findB…
milanmajchrak Jun 23, 2026
3b1ccc0
Document tranches 8 (26 REST + 14 deferred) and 9 (vanilla-file metho…
milanmajchrak Jun 23, 2026
64feeda
Port CLARIN Utils vanilla methods + un-defer 7 REST controllers (DSpa…
milanmajchrak Jun 23, 2026
33e5c9f
Document tranche 10 (Utils vanilla methods + 7 un-deferred controller…
milanmajchrak Jun 23, 2026
b6415e0
Un-defer ClarinGroupRestController + DBConnectionStatisticsController…
milanmajchrak Jun 23, 2026
993bace
Un-defer SolrOAIReindexer + Clarin{Item,EPerson}ImportController (DSp…
milanmajchrak Jun 23, 2026
4c8bc8b
Un-defer ClarinShibbolethLoginFilter (DSpace 9 StatelessLoginFilter c…
milanmajchrak Jun 23, 2026
3e7982e
Un-defer ClarinRefBoxController (DSpace 9 Jena modernization) — BE RE…
milanmajchrak Jun 24, 2026
70daf37
BE write-path PROVEN: admin POST clarinlicenselabel -> 201 -> GET rou…
milanmajchrak Jun 24, 2026
de394ae
Wire CLARIN dspace.cfg keys (BE config tranche)
milanmajchrak Jun 24, 2026
c915c27
Wire CLARIN shibboleth IdP attribute headers (BE config)
milanmajchrak Jun 25, 2026
d6fd7e2
Fix BE integration tests broken by CLARIN config (LanguageSupportIT, …
milanmajchrak Jun 25, 2026
d41b3e1
Fix BE shibboleth ITs: revert committed shib header config to vanilla…
milanmajchrak Jun 26, 2026
1ef0856
Port CLARIN MetadataExposureService change (fixes ResourcePolicyRestR…
milanmajchrak Jun 26, 2026
ffdea5e
docs: CI recovery log — diagnosed + fixed FE (lock/circ-deps/ufal-the…
milanmajchrak Jun 26, 2026
c699e97
docs: record both PRs GREEN/MERGEABLE + remaining DoD work
milanmajchrak Jun 26, 2026
123faf2
CI: don't fail BE build when Codecov upload fails (fork has no CODECO…
milanmajchrak Jun 26, 2026
655e6d1
CI: continue-on-error on the Codecov upload step (guarantee best-effo…
milanmajchrak Jun 26, 2026
58d4987
docs+docker: local v9 stack (compose override, solr-from-source, migr…
milanmajchrak Jun 26, 2026
d43578f
Fix clarinverificationtokens findAll: add @PreAuthorize(ADMIN) (was 5…
milanmajchrak Jun 26, 2026
5b673ec
Port CLARIN file-preview feature (I2) to v9
milanmajchrak Jun 26, 2026
7482eb0
Port CLARIN versioned-handle identifier provider (I5)
milanmajchrak Jun 26, 2026
a4821e9
Port CLARIN PID/EPIC handle minting (P1/P2) to v9
milanmajchrak Jun 26, 2026
1c02798
Port CLARIN Matomo tracking + MetadataBitstreamController (+ ZIP down…
milanmajchrak Jun 26, 2026
2f2dadf
docs: log 2026-06-26 CLARIN feature ports (preview/versioning/PID/mat…
milanmajchrak Jun 26, 2026
30c4b6d
docs: FE preview cluster done end-to-end; shib wiring scoped+deferred…
milanmajchrak Jun 26, 2026
a50849d
Wire ClarinShibbolethLoginFilter (A1/A2) + disable the diverging vani…
milanmajchrak Jun 27, 2026
5e930d1
Complete file-preview S3 path: add S3BitStoreService.getFile (SDK v2)…
milanmajchrak Jun 29, 2026
63639f4
Port CLARIN Health Report + Report Diff (M2)
milanmajchrak Jun 29, 2026
8f491a6
Fix Health/Report scripts breaking ScriptRestRepositoryIT.findAllScri…
milanmajchrak Jun 29, 2026
ab687cb
Defer health-report/report-diff script registration (unblock ScriptRe…
milanmajchrak Jun 29, 2026
d82d88c
Port CLARIN Matomo report-subscription + PDF export (M1 report part)
milanmajchrak Jun 29, 2026
26e4746
Port CLARIN DiscoJuice shibboleth feeds (A1 WAYF) to v9
milanmajchrak Jun 29, 2026
e144989
docs: 2026-06-29 feature completion status + deferred items
milanmajchrak Jun 29, 2026
9fba8a0
DoD: fix findAllScriptsTest (pagination) + re-enable Health/Report sc…
milanmajchrak Jun 29, 2026
368d617
Fix v9 IT regression from clarin-dspace.cfg: neutralize two stale 7.x…
milanmajchrak Jun 29, 2026
388308a
Load clarin-dspace.cfg from local.cfg (deployment), not dspace.cfg (k…
milanmajchrak Jun 29, 2026
41d2ed4
Matomo: use native DSpace 9 tracking, drop redundant CLARIN trackers
milanmajchrak Jun 30, 2026
b865b7a
Matomo: remove the tracker references (factory accessor, beans, contr…
milanmajchrak Jun 30, 2026
ef90e49
Restore CLARIN external/magic-URL handle resolution in HandlePlugin
milanmajchrak Jun 30, 2026
bd972cd
Restore install-time owning-collection signal for multi-prefix PID mi…
milanmajchrak Jun 30, 2026
84a9a03
Document MAJOR review-gap fixes: Matomo→vanilla, HandlePlugin, Handle…
milanmajchrak Jun 30, 2026
73e8e18
Re-port the item-version-linker CLI script (CLARIN O4)
milanmajchrak Jun 30, 2026
02414ef
Revert "Re-port the item-version-linker CLI script (CLARIN O4)"
milanmajchrak Jun 30, 2026
374b883
Document ItemVersionLinker revert: v9 unlink/version-delete semantics…
milanmajchrak Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 8 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,18 @@ jobs:
# Retry action: https://github.com/marketplace/actions/retry-action
# Codecov action: https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
# Best-effort: a Codecov upload failure (e.g. missing CODECOV_TOKEN on this branch/fork)
# must not fail the build — required unit + integration tests determine status.
continue-on-error: true
uses: Wandalen/wretry.action@v3.8.0
with:
action: codecov/codecov-action@v4
# Ensure codecov-action throws an error when it fails to upload
# Do NOT fail the build when the Codecov upload fails. On this fork/branch the
# CODECOV_TOKEN secret is not configured, so the upload always errors; with
# fail_ci_if_error:true that reddened the whole build even though all unit + integration
# tests pass. Coverage upload stays best-effort. (dtq-dev drops the codecov job entirely.)
with: |
fail_ci_if_error: true
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
# Try re-running action 5 times max
attempt_limit: 5
Expand Down
946 changes: 946 additions & 0 deletions CLARIN_DSPACE_V9_PROGRESS.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions docker-compose.clarinv9.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Port + project overrides so the local v9 CLARIN stack does not collide with other DSpace
# stacks already running on this machine (8080/5432/8983 are taken by 8.x stacks).
# Usage: COMPOSE_PROJECT_NAME=clarinv9 docker compose -f docker-compose.yml -f docker-compose.clarinv9.yml up -d
networks:
# other DSpace stacks on this host already use 172.23.0.0/16; pick a free subnet
dspacenet:
ipam:
config:
- subnet: 172.30.0.0/16
services:
dspace:
container_name: clarinv9-dspace
ports: !override
- published: 18080
target: 8080
- published: 18000
target: 8000
environment:
dspace__P__server__P__url: http://localhost:18080/server
dspace__P__ui__P__url: http://localhost:14000
dspacedb:
container_name: clarinv9-dspacedb
ports: !override
- published: 15432
target: 5432
dspacesolr:
container_name: clarinv9-dspacesolr
ports: !override
- published: 18983
target: 8983
28 changes: 28 additions & 0 deletions dspace-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,34 @@
</profiles>

<dependencies>
<!-- CLARIN/LINDAT (UFAL) feature dependencies (ported from dtq-dev for DSpace 9 upgrade) -->
<dependency>
<!-- PDF generation for Matomo report exporter -->
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.4</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jcommon</artifactId>
<version>1.0.24</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>com.flipkart.zjsonpatch</groupId>
<artifactId>zjsonpatch</artifactId>
<version>0.4.16</version>
</dependency>
<dependency>
<!-- JWT for Clarin tokens / Personal Access Tokens (PAT) -->
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose-jwt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.administer;

import static org.dspace.administer.ClarinTokenCreator.getExpirationDate;
import static org.dspace.administer.ClarinTokenCreator.getMaskedToken;

import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import com.nimbusds.jose.EncryptionMethod;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.factory.ClarinServiceFactory;
import org.dspace.content.service.clarin.ClarinTokenService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;

/**
* This admin script is used to manage(create or invalidate) clarin tokens, for the user specified by ID or e-mail.
* <p>
* For the token creation, the user and the expiration time must be set in script options.
* Created token string is then printed on the console, and admin can send it to the user (e.g. via the e-mail).
* <p>
* For the token invalidation, either the token string or the user need to be set in script options.
* When neither token nor user is set, all tokens are deleted.
* <p>
* Admin user can also use this script to generate encryption/decryption secret key and store it into
* "clarin.token.encryption.secret" config property.
*
* @author Milan Kuchtiak
*/
public class ClarinTokenAdministrator {

private static final Logger log = LogManager.getLogger(ClarinTokenAdministrator.class);

private ClarinTokenAdministrator() {
}

public static void main(String args[]) throws Exception {
log.info("Clarin Token administrator started ....");

Options options = new Options();
options.addOption("c", "create", false, "create token for ePerson specified by ID or email");
options.addOption("d", "delete", false,
"delete specified token, or delete all tokens for given ePerson, " +
"or delete all tokens when -t, -u, and -e options are missing)");
options.addOption("g", "generateEncryptionKey", false,
"generate encryption/decryption secret key for clarin.token.encryption.secret property");
options.addOption("u", "ePerson_ID", true, "ePerson UUID");
options.addOption("e", "email", true, "ePerson email");
options.addOption("x", "expiration", true,
"token expiration time in days or hours, (e.g. 3d or 48h), for -c option only [required for create]");
options.addOption("t", "token", true,
"token string [optional for delete]");
options.addOption("h", "help", false, "help");

CommandLineParser parser = new DefaultParser();
try {
CommandLine line = parser.parse(options, args);
if (line.hasOption('h') || (!line.hasOption('c') && !line.hasOption('d') && !line.hasOption('g')) ) {
printHelpAndExit(options);
}
boolean isCreate = line.hasOption('c');
boolean isDelete = line.hasOption('d');
boolean generateEncryptionKey = line.hasOption('g');

if (isCreate && isDelete || isCreate && generateEncryptionKey || isDelete && generateEncryptionKey) {
throw new ParseException("Create, delete and generate options are mutually exclusive");
}

if (isCreate && !line.hasOption("u") && !line.hasOption("e")) {
throw new ParseException("either ePerson UUID or ePerson e-mail option is needed to create token");
}

if (isCreate && !line.hasOption("x")) {
throw new ParseException("Token expiration time option is missing");
}

UUID ePersonUUID = null;
if (line.hasOption("u")) {
ePersonUUID = UUID.fromString(line.getOptionValue("u"));
}

String email = null;
if (line.hasOption("e")) {
email = line.getOptionValue("e");
}

String token = null;
if (line.hasOption("t")) {
token = line.getOptionValue("t");
}

ClarinTokenService clarinTokenService =
ClarinServiceFactory.getInstance().getClarinTokenService();
EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();

try (Context context = new Context()) {
try {
context.turnOffAuthorisationSystem();
EPerson ePerson = getEPerson(context, ePersonService, ePersonUUID, email);
if (isCreate) {
if (ePerson == null) {
throw new IllegalArgumentException("Invalid ePerson UUID or email");
}
Date expirationDate = getExpirationDate(line.getOptionValue("x").toLowerCase());
createToken(context, clarinTokenService, ePerson, expirationDate);
} else if (isDelete) {
deleteToken(context, clarinTokenService, token, ePerson);
} else {
generateEncryptionKey();
}
} finally {
context.restoreAuthSystemState();
context.complete();
}
}

} catch (ParseException e) {
System.out.printf("Invalid command options: %s\n", e.getMessage());
printHelpAndExit(options);
}

log.info("Clarin Token administrator finished.");
}

private static void createToken(Context context,
ClarinTokenService clarinTokenService,
EPerson ePerson,
Date expirationDate) throws SQLException, AuthorizeException {
String token = clarinTokenService.createToken(context, ePerson, expirationDate);
log.debug("Clarin Token created: {}", getMaskedToken(token));
System.out.printf("Clarin Token created: %s\n", token);
System.out.printf("For user: %s, with ID: %s\n", ePerson.getEmail(), ePerson.getID());
}

private static void deleteToken(Context context,
ClarinTokenService clarinTokenService,
String token,
EPerson ePerson) throws SQLException, AuthorizeException {
if (token != null) {
clarinTokenService.delete(context, token);
System.out.println("Clarin Token removed.");
} else if (ePerson != null) {
clarinTokenService.delete(context, ePerson);
System.out.println("Clarin Tokens removed.");
System.out.printf("For user: %s, with ID: %s\n", ePerson.getEmail(), ePerson.getID());
} else {
clarinTokenService.deleteAll(context);
System.out.println("All Clarin Tokens removed");
}
}

private static void generateEncryptionKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(EncryptionMethod.A256GCM.cekBitLength());
SecretKey aesKey = keyGen.generateKey();

String encodedAesKey = Base64.getEncoder().encodeToString(aesKey.getEncoded());
log.debug("Encryption Key generated: {}", getMaskedToken(encodedAesKey));
System.out.printf("Encryption Key: %s\n", encodedAesKey);
}


private static void printHelpAndExit(Options options) {
// print the help message
HelpFormatter myHelp = new HelpFormatter();
myHelp.printHelp("clarin-token\n", options);
System.exit(0);
}

private static EPerson getEPerson(Context context, EPersonService ePersonService, UUID ePersonID, String email)
throws SQLException {
return ePersonID == null ? ePersonService.findByEmail(context, email) : ePersonService.find(context, ePersonID);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.administer;

import java.util.List;

import org.apache.commons.cli.Options;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.configuration.ScriptConfiguration;

/**
* This is a ScriptConfiguration for {@link ClarinTokenCreator}.
*
* @author Milan Kuchtiak
*/
public class ClarinTokenConfiguration extends ScriptConfiguration<ClarinTokenCreator> {

private Class<ClarinTokenCreator> dspaceRunnableClass;

/**
* Generic getter for the dspaceRunnableClass
*
* @return the dspaceRunnableClass value of this ScriptConfiguration
*/
@Override
public Class<ClarinTokenCreator> getDspaceRunnableClass() {
return dspaceRunnableClass;
}

/**
* Generic setter for the dspaceRunnableClass
*
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration
*/
@Override
public void setDspaceRunnableClass(Class<ClarinTokenCreator> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}

/**
* This script is allowed to execute to any authorized user. Further access control mechanism then checks,
* if the current user is authorized to download a file to the item specified in command line parameters.
*
* @param context The relevant DSpace context
* @param commandLineParameters the parameters that will be used to start the process if known,
* <code>null</code> otherwise
* @return A boolean indicating whether the script is allowed to execute or not
*/
@Override
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
return context.getCurrentUser() != null;
}

/**
* The getter for the options of the Script
*
* @return the options value of this ScriptConfiguration
*/
@Override
public Options getOptions() {
if (options == null) {

Options options = new Options();

options.addOption("h", "help", false, "help");

options.addOption("c", "create", false, "create new token");
options.addOption("d", "delete", false, "delete/deactivate token");

options.addOption("x", "expiration", true,
"token expiration in days or hours, e.g. 3d or 48h [required for token create]");
options.addOption("e", "email", true,
"e-mail to send newly created access token [optional for token create]");
options.addOption("t", "token", true, "token to delete/deactivate [required for token delete]");

super.options = options;
}
return options;
}
}
Loading
Loading