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

knowledgepixels / nanodash / 24787247242

22 Apr 2026 03:31PM UTC coverage: 17.496% (+0.03%) from 17.465%
24787247242

push

github

web-flow
Merge pull request #449 from knowledgepixels/remove-formobj-use-pagereference

refactor: drop formobj, use PageReference for preview Back

899 of 6170 branches covered (14.57%)

Branch coverage included in aggregate %.

2155 of 11285 relevant lines covered (19.1%)

2.64 hits per line

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

15.02
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.Map;
33
import java.util.concurrent.ConcurrentHashMap;
34
import java.util.concurrent.ConcurrentMap;
35

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

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

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

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

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

69
    private static ValueFactory vf = SimpleValueFactory.getInstance();
9✔
70

71
//        private IntroExtractor introExtractor;
72

73
    private String userDir = System.getProperty("user.home") + "/.nanopub/";
15✔
74
    private NanopubResults.ViewMode nanopubResultsViewMode = NanopubResults.ViewMode.LIST;
9✔
75

76
    private KeyPair keyPair;
77
    private IRI userIri;
78
    private ConcurrentMap<IRI, IntroNanopub> introNps;
79
//        private Boolean isOrcidLinked;
80
//        private String orcidLinkError;
81

82
    private Integer localIntroCount = null;
9✔
83
    private IntroNanopub localIntro = null;
9✔
84

85
    private Date lastTimeIntroPublished = null;
9✔
86

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

132
    /**
133
     * Checks if the user's profile is complete.
134
     *
135
     * @return True if the profile is complete, false otherwise.
136
     */
137
    public boolean isProfileComplete() {
138
        return userIri != null && keyPair != null && introNps != null;
×
139
    }
140

141
    /**
142
     * Redirects the user to the login page if their profile is incomplete.
143
     *
144
     * @param path       The path to redirect to after login.
145
     * @param parameters The page parameters for the redirect.
146
     */
147
    public void redirectToLoginIfNeeded(String path, PageParameters parameters) {
148
        String loginUrl = getLoginUrl(path, parameters);
×
149
        if (loginUrl == null) return;
×
150
        throw new RedirectToUrlException(loginUrl);
×
151
    }
152

153
    /**
154
     * Retrieves the login URL for the user.
155
     *
156
     * @param path       The path to redirect to after login.
157
     * @param parameters The page parameters for the redirect.
158
     * @return The login URL, or null if the user is already logged in.
159
     */
160
    public String getLoginUrl(String path, PageParameters parameters) {
161
        if (isProfileComplete()) return null;
×
162
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
163
            return OrcidLoginPage.getOrcidLoginUrl(path, parameters);
×
164
        } else {
165
            return ProfilePage.MOUNT_PATH;
×
166
        }
167
    }
168

169
    /**
170
     * Retrieves the public key as a Base64-encoded string.
171
     *
172
     * @return The public key string, or null if the key pair is not set.
173
     */
174
    public String getPubkeyString() {
175
        if (keyPair == null) return null;
×
176
        return DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded()).replaceAll("\\s", "");
×
177
    }
178

179
    /**
180
     * Retrieves the public key hash for the user.
181
     *
182
     * @return The SHA-256 hash of the public key, or null if the public key is not set.
183
     */
184
    public String getPubkeyhash() {
185
        String pubkey = getPubkeyString();
×
186
        if (pubkey == null) return null;
×
187
        return Utils.createSha256HexHash(pubkey);
×
188
    }
189

190
    /**
191
     * Checks if the user's public key is approved.
192
     *
193
     * @return True if the public key is approved, false otherwise.
194
     */
195
    public boolean isPubkeyApproved() {
196
        if (keyPair == null || userIri == null) return false;
×
197
        return User.isApprovedPubkeyhashForUser(getPubkeyhash(), userIri);
×
198
    }
199

200
    /**
201
     * Retrieves the user's key pair.
202
     *
203
     * @return The key pair.
204
     */
205
    public KeyPair getKeyPair() {
206
        return keyPair;
×
207
    }
208

209
    /**
210
     * Generates a new key pair for the user.
211
     */
212
    public void makeKeys() {
213
        try {
214
            MakeKeys.make(getKeyFile().getAbsolutePath().replaceFirst("_rsa$", ""), SignatureAlgorithm.RSA);
×
215
            keyPair = SignNanopub.loadKey(getKeyFile().getPath(), SignatureAlgorithm.RSA);
×
216
        } catch (Exception ex) {
×
217
            logger.error("Couldn't create key pair", ex);
×
218
        }
×
219
    }
×
220

221
    /**
222
     * Retrieves the user's IRI.
223
     *
224
     * @return The user's IRI, or null if not set.
225
     */
226
    public IRI getUserIri() {
227
        return userIri;
9✔
228
    }
229

230
    /**
231
     * Retrieves the user's introduction nanopublications.
232
     *
233
     * @return A list of user's introduction nanopublications.
234
     */
235
    public List<IntroNanopub> getUserIntroNanopubs() {
236
        return User.getIntroNanopubs(userIri);
×
237
    }
238

239
    /**
240
     * Counts the number of local introduction nanopublications.
241
     *
242
     * @return The count of local introduction nanopublications.
243
     */
244
    public int getLocalIntroCount() {
245
        if (localIntroCount == null) {
×
246
            localIntroCount = 0;
×
247
            for (IntroNanopub inp : getUserIntroNanopubs()) {
×
248
                if (isIntroWithLocalKey(inp)) {
×
249
                    localIntroCount++;
×
250
                    localIntro = inp;
×
251
                }
252
            }
×
253
            if (localIntroCount > 1) localIntro = null;
×
254
        }
255
        return localIntroCount;
×
256
    }
257

258
    /**
259
     * Retrieves the local introduction nanopublication.
260
     *
261
     * @return The local introduction nanopublication, or null if not found.
262
     */
263
    public IntroNanopub getLocalIntro() {
264
        getLocalIntroCount();
×
265
        return localIntro;
×
266
    }
267

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

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

316
    /**
317
     * Logs out the user and invalidates the session.
318
     */
319
    public void logout() {
320
        userIri = null;
×
321
        invalidateNow();
×
322
    }
×
323

324
    /**
325
     * Retrieves the user's introduction nanopublications as a map.
326
     *
327
     * @return A map of introduction nanopublications.
328
     */
329
    public Map<IRI, IntroNanopub> getIntroNanopubs() {
330
        return introNps;
×
331
    }
332

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

378
    /**
379
     * Retrieves the file for storing the user's ORCID identifier.
380
     *
381
     * @return The ORCID file.
382
     */
383
    private File getOrcidFile() {
384
        return new File(userDir + "orcid");
21✔
385
    }
386

387
    /**
388
     * Retrieves the file for storing the user's private key.
389
     *
390
     * @return The key file.
391
     */
392
    public File getKeyFile() {
393
        return new File(userDir + "id_rsa");
×
394
    }
395

396
    /**
397
     * Sets the time when the introduction was last published.
398
     */
399
    public void setIntroPublishedNow() {
400
        lastTimeIntroPublished = new Date();
×
401
    }
×
402

403
    /**
404
     * Checks if the introduction has been published.
405
     *
406
     * @return True if the introduction has been published, false otherwise.
407
     */
408
    public boolean hasIntroPublished() {
409
        return lastTimeIntroPublished != null;
×
410
    }
411

412
    /**
413
     * Calculates the time since the last introduction was published.
414
     *
415
     * @return The time in milliseconds since the last introduction was published, or Long.MAX_VALUE if it has never been published.
416
     */
417
    public long getTimeSinceLastIntroPublished() {
418
        if (lastTimeIntroPublished == null) return Long.MAX_VALUE;
×
419
        return new Date().getTime() - lastTimeIntroPublished.getTime();
×
420
    }
421

422
    /**
423
     * Sets the view mode for displaying nanopublication results.
424
     *
425
     * @param viewMode The desired view mode (e.g., GRID or LIST).
426
     */
427
    public void setNanopubResultsViewMode(NanopubResults.ViewMode viewMode) {
428
        this.nanopubResultsViewMode = viewMode;
×
429
    }
×
430

431
    /**
432
     * Retrieves the current view mode for displaying nanopublication results.
433
     *
434
     * @return The current view mode.
435
     */
436
    public NanopubResults.ViewMode getNanopubResultsViewMode() {
437
        return this.nanopubResultsViewMode;
×
438
    }
439

440
    // --- Preview nanopub support ---
441

442
    public static class PreviewNanopub implements Serializable {
443
        private final Nanopub nanopub;
444
        private final PageParameters pageParams;
445
        private final Class<? extends WebPage> confirmPageClass;
446
        private final boolean consentChecked;
447
        private final PageReference sourcePageRef;
448

449
        public PreviewNanopub(Nanopub nanopub, PageParameters pageParams, Class<? extends WebPage> confirmPageClass, boolean consentChecked, PageReference sourcePageRef) {
×
450
            this.nanopub = nanopub;
×
451
            this.pageParams = pageParams;
×
452
            this.confirmPageClass = confirmPageClass;
×
453
            this.consentChecked = consentChecked;
×
454
            this.sourcePageRef = sourcePageRef;
×
455
        }
×
456

457
        public Nanopub getNanopub() { return nanopub; }
×
458
        public PageParameters getPageParams() { return pageParams; }
×
459
        public Class<? extends WebPage> getConfirmPageClass() { return confirmPageClass; }
×
460
        public boolean isConsentChecked() { return consentChecked; }
×
461
        public PageReference getSourcePageRef() { return sourcePageRef; }
×
462
    }
463

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

466
    public void setPreviewNanopub(String id, PreviewNanopub preview) {
467
        previewMap.put(id, preview);
×
468
    }
×
469

470
    public PreviewNanopub getPreviewNanopub(String id) {
471
        return previewMap.get(id);
×
472
    }
473

474
    public PreviewNanopub removePreviewNanopub(String id) {
475
        return previewMap.remove(id);
×
476
    }
477

478
}
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