• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 25386070558

05 May 2026 03:34PM UTC coverage: 18.296% (+0.009%) from 18.287%
25386070558

push

github

tkuhn
Merge branch 'master' of github.com:knowledgepixels/nanodash

956 of 6218 branches covered (15.37%)

Branch coverage included in aggregate %.

2253 of 11321 relevant lines covered (19.9%)

2.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.81
src/main/java/com/knowledgepixels/nanodash/NanodashSession.java
1
package com.knowledgepixels.nanodash;
2

3
import com.knowledgepixels.nanodash.component.NanopubResults;
4
import com.knowledgepixels.nanodash.domain.User;
5
import com.knowledgepixels.nanodash.page.OrcidLoginPage;
6
import com.knowledgepixels.nanodash.page.ProfilePage;
7
import jakarta.servlet.http.HttpServletRequest;
8
import jakarta.servlet.http.HttpSession;
9
import jakarta.xml.bind.DatatypeConverter;
10
import org.apache.commons.io.FileUtils;
11
import org.apache.wicket.PageReference;
12
import org.apache.wicket.Session;
13
import org.apache.wicket.protocol.http.WebSession;
14
import org.apache.wicket.request.Request;
15
import org.apache.wicket.request.flow.RedirectToUrlException;
16
import org.apache.wicket.request.mapper.parameter.PageParameters;
17
import org.eclipse.rdf4j.model.IRI;
18
import org.eclipse.rdf4j.model.ValueFactory;
19
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
20
import org.nanopub.extra.security.*;
21
import org.nanopub.extra.setting.IntroNanopub;
22
import org.slf4j.Logger;
23
import org.slf4j.LoggerFactory;
24

25
import java.io.File;
26
import java.io.IOException;
27
import java.io.Serializable;
28
import java.nio.charset.StandardCharsets;
29
import java.security.KeyPair;
30
import java.util.Date;
31
import java.util.List;
32
import java.util.Locale;
33
import java.util.Map;
34
import java.util.concurrent.ConcurrentHashMap;
35
import java.util.concurrent.ConcurrentMap;
36

37
import org.apache.wicket.markup.html.WebPage;
38
import org.nanopub.Nanopub;
39

40
/**
41
 * Represents a session in the Nanodash application.
42
 */
43
public class NanodashSession extends WebSession {
44

45
    private transient HttpSession httpSession;
46
    private static final Logger logger = LoggerFactory.getLogger(NanodashSession.class);
9✔
47

48
    /**
49
     * Retrieves the current Nanodash session.
50
     *
51
     * @return The current NanodashSession instance.
52
     */
53
    public static NanodashSession get() {
54
        return (NanodashSession) Session.get();
9✔
55
    }
56

57
    /**
58
     * Constructs a new NanodashSession for the given request.
59
     * Initializes the HTTP session and loads profile information.
60
     *
61
     * @param request The HTTP request.
62
     */
63
    public NanodashSession(Request request) {
64
        super(request);
9✔
65
        setLocale(Locale.US);
12✔
66
        httpSession = ((HttpServletRequest) request.getContainerRequest()).getSession();
18✔
67
        bind();
6✔
68
        loadProfileInfo();
6✔
69
    }
3✔
70

71
    @Override
72
    public Session setLocale(Locale locale) {
73
        return super.setLocale(Locale.US);
12✔
74
    }
75

76
    private static ValueFactory vf = SimpleValueFactory.getInstance();
9✔
77

78
//        private IntroExtractor introExtractor;
79

80
    private String userDir = System.getProperty("user.home") + "/.nanopub/";
15✔
81
    private NanopubResults.ViewMode nanopubResultsViewMode = NanopubResults.ViewMode.LIST;
9✔
82

83
    private KeyPair keyPair;
84
    private IRI userIri;
85
    private ConcurrentMap<IRI, IntroNanopub> introNps;
86
//        private Boolean isOrcidLinked;
87
//        private String orcidLinkError;
88

89
    private Integer localIntroCount = null;
9✔
90
    private IntroNanopub localIntro = null;
9✔
91

92
    private Date lastTimeIntroPublished = null;
9✔
93

94
    /**
95
     * Loads profile information for the user.
96
     * Initializes user-related data such as keys and introductions.
97
     */
98
    public void loadProfileInfo() {
99
        localIntroCount = null;
9✔
100
        localIntro = null;
9✔
101
        NanodashPreferences prefs = NanodashPreferences.get();
6✔
102
        if (prefs.isOrcidLoginMode()) {
9!
103
            File usersDir = new File(System.getProperty("user.home") + "/.nanopub/nanodash-users/");
×
104
            if (!usersDir.exists()) usersDir.mkdir();
×
105
        }
106
        if (userIri == null && !prefs.isReadOnlyMode() && !prefs.isOrcidLoginMode()) {
27!
107
            if (getOrcidFile().exists()) {
12!
108
                try {
109
                    String orcid = FileUtils.readFileToString(getOrcidFile(), StandardCharsets.UTF_8).trim();
×
110
                    //String orcid = Files.readString(orcidFile.toPath(), StandardCharsets.UTF_8).trim();
111
                    if (orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
112
                        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
113
                        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
114
                    }
115
                } catch (IOException ex) {
×
116
                    logger.error("Couldn't read ORCID file", ex);
×
117
                }
×
118
            }
119
        }
120
        if (userIri != null && keyPair == null) {
9!
121
            File keyFile = getKeyFile();
×
122
            if (keyFile.exists()) {
×
123
                try {
124
                    keyPair = SignNanopub.loadKey(keyFile.getPath(), SignatureAlgorithm.RSA);
×
125
                } catch (Exception ex) {
×
126
                    logger.error("Couldn't load key pair", ex);
×
127
                }
×
128
            } else {
129
                // Automatically generate new keys
130
                makeKeys();
×
131
            }
132
        }
133
        if (userIri != null && keyPair != null && introNps == null) {
9!
134
            introNps = new ConcurrentHashMap<>(User.getIntroNanopubs(getPubkeyString()));
×
135
        }
136
//                checkOrcidLink();
137
    }
3✔
138

139
    /**
140
     * Checks if the user's profile is complete.
141
     *
142
     * @return True if the profile is complete, false otherwise.
143
     */
144
    public boolean isProfileComplete() {
145
        return userIri != null && keyPair != null && introNps != null;
×
146
    }
147

148
    /**
149
     * Redirects the user to the login page if their profile is incomplete.
150
     *
151
     * @param path       The path to redirect to after login.
152
     * @param parameters The page parameters for the redirect.
153
     */
154
    public void redirectToLoginIfNeeded(String path, PageParameters parameters) {
155
        String loginUrl = getLoginUrl(path, parameters);
×
156
        if (loginUrl == null) return;
×
157
        throw new RedirectToUrlException(loginUrl);
×
158
    }
159

160
    /**
161
     * Retrieves the login URL for the user.
162
     *
163
     * @param path       The path to redirect to after login.
164
     * @param parameters The page parameters for the redirect.
165
     * @return The login URL, or null if the user is already logged in.
166
     */
167
    public String getLoginUrl(String path, PageParameters parameters) {
168
        if (isProfileComplete()) return null;
×
169
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
170
            return OrcidLoginPage.getOrcidLoginUrl(path, parameters);
×
171
        } else {
172
            return ProfilePage.MOUNT_PATH;
×
173
        }
174
    }
175

176
    /**
177
     * Retrieves the public key as a Base64-encoded string.
178
     *
179
     * @return The public key string, or null if the key pair is not set.
180
     */
181
    public String getPubkeyString() {
182
        if (keyPair == null) return null;
×
183
        return DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded()).replaceAll("\\s", "");
×
184
    }
185

186
    /**
187
     * Retrieves the public key hash for the user.
188
     *
189
     * @return The SHA-256 hash of the public key, or null if the public key is not set.
190
     */
191
    public String getPubkeyhash() {
192
        String pubkey = getPubkeyString();
×
193
        if (pubkey == null) return null;
×
194
        return Utils.createSha256HexHash(pubkey);
×
195
    }
196

197
    /**
198
     * Checks if the user's public key is approved.
199
     *
200
     * @return True if the public key is approved, false otherwise.
201
     */
202
    public boolean isPubkeyApproved() {
203
        if (keyPair == null || userIri == null) return false;
×
204
        return User.isApprovedPubkeyhashForUser(getPubkeyhash(), userIri);
×
205
    }
206

207
    /**
208
     * Retrieves the user's key pair.
209
     *
210
     * @return The key pair.
211
     */
212
    public KeyPair getKeyPair() {
213
        return keyPair;
×
214
    }
215

216
    /**
217
     * Generates a new key pair for the user.
218
     */
219
    public void makeKeys() {
220
        try {
221
            MakeKeys.make(getKeyFile().getAbsolutePath().replaceFirst("_rsa$", ""), SignatureAlgorithm.RSA);
×
222
            keyPair = SignNanopub.loadKey(getKeyFile().getPath(), SignatureAlgorithm.RSA);
×
223
        } catch (Exception ex) {
×
224
            logger.error("Couldn't create key pair", ex);
×
225
        }
×
226
    }
×
227

228
    /**
229
     * Retrieves the user's IRI.
230
     *
231
     * @return The user's IRI, or null if not set.
232
     */
233
    public IRI getUserIri() {
234
        return userIri;
9✔
235
    }
236

237
    /**
238
     * Retrieves the user's introduction nanopublications.
239
     *
240
     * @return A list of user's introduction nanopublications.
241
     */
242
    public List<IntroNanopub> getUserIntroNanopubs() {
243
        return User.getIntroNanopubs(userIri);
×
244
    }
245

246
    /**
247
     * Counts the number of local introduction nanopublications.
248
     *
249
     * @return The count of local introduction nanopublications.
250
     */
251
    public int getLocalIntroCount() {
252
        if (localIntroCount == null) {
×
253
            localIntroCount = 0;
×
254
            for (IntroNanopub inp : getUserIntroNanopubs()) {
×
255
                if (isIntroWithLocalKey(inp)) {
×
256
                    localIntroCount++;
×
257
                    localIntro = inp;
×
258
                }
259
            }
×
260
            if (localIntroCount > 1) localIntro = null;
×
261
        }
262
        return localIntroCount;
×
263
    }
264

265
    /**
266
     * Retrieves the local introduction nanopublication.
267
     *
268
     * @return The local introduction nanopublication, or null if not found.
269
     */
270
    public IntroNanopub getLocalIntro() {
271
        getLocalIntroCount();
×
272
        return localIntro;
×
273
    }
274

275
    /**
276
     * Checks if the given introduction nanopublication is associated with the local key.
277
     *
278
     * @param inp The introduction nanopublication.
279
     * @return True if associated with the local key, false otherwise.
280
     */
281
    public boolean isIntroWithLocalKey(IntroNanopub inp) {
282
        IRI location = Utils.getLocation(inp);
×
283
        NanopubSignatureElement el = Utils.getNanopubSignatureElement(inp);
×
284
        String siteUrl = NanodashPreferences.get().getWebsiteUrl();
×
285
        if (location != null && siteUrl != null) {
×
286
            String l = location.stringValue();
×
287
            // TODO: Solve the name change recognition in a better way:
288
            if (!l.equals(siteUrl) && !l.replace("nanobench", "nanodash").equals(siteUrl)) return false;
×
289
        }
290
        if (!getPubkeyString().equals(el.getPublicKeyString())) return false;
×
291
        for (KeyDeclaration kd : inp.getKeyDeclarations()) {
×
292
            if (getPubkeyString().equals(kd.getPublicKeyString())) return true;
×
293
        }
×
294
        return false;
×
295
    }
296

297
    /**
298
     * Sets the user's ORCID identifier.
299
     *
300
     * @param orcid The ORCID identifier.
301
     */
302
    public void setOrcid(String orcid) {
303
        if (!orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
304
            throw new RuntimeException("Illegal ORCID identifier: " + orcid);
×
305
        }
306
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
307
            userDir = System.getProperty("user.home") + "/.nanopub/nanodash-users/" + orcid + "/";
×
308
            File f = new File(userDir);
×
309
            if (!f.exists()) f.mkdir();
×
310
        } else {
×
311
            try {
312
                FileUtils.writeStringToFile(getOrcidFile(), orcid + "\n", StandardCharsets.UTF_8);
×
313
                //                        Files.writeString(orcidFile.toPath(), orcid + "\n");
314
            } catch (IOException ex) {
×
315
                logger.error("Couldn't write ORCID file", ex);
×
316
            }
×
317
        }
318
        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
319
        loadProfileInfo();
×
320
        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
321
    }
×
322

323
    /**
324
     * Logs out the user and invalidates the session.
325
     */
326
    public void logout() {
327
        userIri = null;
×
328
        invalidateNow();
×
329
    }
×
330

331
    /**
332
     * Retrieves the user's introduction nanopublications as a map.
333
     *
334
     * @return A map of introduction nanopublications.
335
     */
336
    public Map<IRI, IntroNanopub> getIntroNanopubs() {
337
        return introNps;
×
338
    }
339

340
//        public void checkOrcidLink() {
341
//                if (isOrcidLinked == null && userIri != null) {
342
//                        orcidLinkError = "";
343
//                        introExtractor = null;
344
//                        try {
345
//                                introExtractor = IntroNanopub.extract(userIri.stringValue(), null);
346
//                                if (introExtractor.getIntroNanopub() == null) {
347
//                                        orcidLinkError = "ORCID account is not linked.";
348
//                                        isOrcidLinked = false;
349
//                                } else {
350
//                                        IntroNanopub inp = IntroNanopub.get(userIri.stringValue(), introExtractor);
351
//                                        if (introNps != null && introNps.containsKey(inp.getNanopub().getUri())) {
352
//                                                // TODO: also check whether introduction contains local key
353
//                                                isOrcidLinked = true;
354
//                                        } else {
355
//                                                isOrcidLinked = false;
356
//                                                orcidLinkError = "Error: ORCID is linked to another introduction nanopublication.";
357
//                                        }
358
//                                }
359
//                        } catch (Exception ex) {
360
//                                logger.error("ORCID check failed");
361
//                                orcidLinkError = "ORCID check failed.";
362
//                        }
363
//                }
364
//        }
365
//
366
//        public void resetOrcidLinked() {
367
//                isOrcidLinked = null;
368
//        }
369
//
370
//        public boolean isOrcidLinked() {
371
//                checkOrcidLink();
372
//                return isOrcidLinked != null && isOrcidLinked == true;
373
//        }
374
//
375
//        public String getOrcidLinkError() {
376
//                return orcidLinkError;
377
//        }
378
//
379
//        public String getOrcidName() {
380
//                if (introExtractor == null || introExtractor.getName() == null) return null;
381
//                if (introExtractor.getName().trim().isEmpty()) return null;
382
//                return introExtractor.getName();
383
//        }
384

385
    /**
386
     * Retrieves the file for storing the user's ORCID identifier.
387
     *
388
     * @return The ORCID file.
389
     */
390
    private File getOrcidFile() {
391
        return new File(userDir + "orcid");
21✔
392
    }
393

394
    /**
395
     * Retrieves the file for storing the user's private key.
396
     *
397
     * @return The key file.
398
     */
399
    public File getKeyFile() {
400
        return new File(userDir + "id_rsa");
×
401
    }
402

403
    /**
404
     * Sets the time when the introduction was last published.
405
     */
406
    public void setIntroPublishedNow() {
407
        lastTimeIntroPublished = new Date();
×
408
    }
×
409

410
    /**
411
     * Checks if the introduction has been published.
412
     *
413
     * @return True if the introduction has been published, false otherwise.
414
     */
415
    public boolean hasIntroPublished() {
416
        return lastTimeIntroPublished != null;
×
417
    }
418

419
    /**
420
     * Calculates the time since the last introduction was published.
421
     *
422
     * @return The time in milliseconds since the last introduction was published, or Long.MAX_VALUE if it has never been published.
423
     */
424
    public long getTimeSinceLastIntroPublished() {
425
        if (lastTimeIntroPublished == null) return Long.MAX_VALUE;
×
426
        return new Date().getTime() - lastTimeIntroPublished.getTime();
×
427
    }
428

429
    /**
430
     * Sets the view mode for displaying nanopublication results.
431
     *
432
     * @param viewMode The desired view mode (e.g., GRID or LIST).
433
     */
434
    public void setNanopubResultsViewMode(NanopubResults.ViewMode viewMode) {
435
        this.nanopubResultsViewMode = viewMode;
×
436
    }
×
437

438
    /**
439
     * Retrieves the current view mode for displaying nanopublication results.
440
     *
441
     * @return The current view mode.
442
     */
443
    public NanopubResults.ViewMode getNanopubResultsViewMode() {
444
        return this.nanopubResultsViewMode;
×
445
    }
446

447
    // --- Preview nanopub support ---
448

449
    public static class PreviewNanopub implements Serializable {
450
        private final Nanopub nanopub;
451
        private final PageParameters pageParams;
452
        private final Class<? extends WebPage> confirmPageClass;
453
        private final boolean consentChecked;
454
        private final PageReference sourcePageRef;
455

456
        public PreviewNanopub(Nanopub nanopub, PageParameters pageParams, Class<? extends WebPage> confirmPageClass, boolean consentChecked, PageReference sourcePageRef) {
×
457
            this.nanopub = nanopub;
×
458
            this.pageParams = pageParams;
×
459
            this.confirmPageClass = confirmPageClass;
×
460
            this.consentChecked = consentChecked;
×
461
            this.sourcePageRef = sourcePageRef;
×
462
        }
×
463

464
        public Nanopub getNanopub() { return nanopub; }
×
465
        public PageParameters getPageParams() { return pageParams; }
×
466
        public Class<? extends WebPage> getConfirmPageClass() { return confirmPageClass; }
×
467
        public boolean isConsentChecked() { return consentChecked; }
×
468
        public PageReference getSourcePageRef() { return sourcePageRef; }
×
469
    }
470

471
    private ConcurrentMap<String, PreviewNanopub> previewMap = new ConcurrentHashMap<>();
15✔
472

473
    public void setPreviewNanopub(String id, PreviewNanopub preview) {
474
        previewMap.put(id, preview);
×
475
    }
×
476

477
    public PreviewNanopub getPreviewNanopub(String id) {
478
        return previewMap.get(id);
×
479
    }
480

481
    public PreviewNanopub removePreviewNanopub(String id) {
482
        return previewMap.remove(id);
×
483
    }
484

485
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc