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

knowledgepixels / nanodash / 17837235071

18 Sep 2025 05:58PM UTC coverage: 13.87%. Remained the same
17837235071

push

github

tkuhn
chore: Remove serialVersionUIDs

443 of 4022 branches covered (11.01%)

Branch coverage included in aggregate %.

1133 of 7341 relevant lines covered (15.43%)

0.68 hits per line

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

14.93
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.component.PublishForm;
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.Session;
12
import org.apache.wicket.protocol.http.WebSession;
13
import org.apache.wicket.request.Request;
14
import org.apache.wicket.request.flow.RedirectToUrlException;
15
import org.apache.wicket.request.mapper.parameter.PageParameters;
16
import org.eclipse.rdf4j.model.IRI;
17
import org.eclipse.rdf4j.model.ValueFactory;
18
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
19
import org.nanopub.extra.security.*;
20
import org.nanopub.extra.setting.IntroNanopub;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23

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

34
/**
35
 * Represents a session in the Nanodash application.
36
 */
37
public class NanodashSession extends WebSession {
38

39
    private transient HttpSession httpSession;
40
    private static final Logger logger = LoggerFactory.getLogger(NanodashSession.class);
3✔
41

42
    /**
43
     * Retrieves the current Nanodash session.
44
     *
45
     * @return The current NanodashSession instance.
46
     */
47
    public static NanodashSession get() {
48
        return (NanodashSession) Session.get();
×
49
    }
50

51
    /**
52
     * Constructs a new NanodashSession for the given request.
53
     * Initializes the HTTP session and loads profile information.
54
     *
55
     * @param request The HTTP request.
56
     */
57
    public NanodashSession(Request request) {
58
        super(request);
3✔
59
        httpSession = ((HttpServletRequest) request.getContainerRequest()).getSession();
6✔
60
        bind();
2✔
61
        loadProfileInfo();
2✔
62
    }
1✔
63

64
    private static ValueFactory vf = SimpleValueFactory.getInstance();
3✔
65

66
//        private IntroExtractor introExtractor;
67

68
    private String userDir = System.getProperty("user.home") + "/.nanopub/";
5✔
69
    private NanopubResults.ViewMode nanopubResultsViewMode = NanopubResults.ViewMode.GRID;
3✔
70

71
    private KeyPair keyPair;
72
    private IRI userIri;
73
    private ConcurrentMap<IRI, IntroNanopub> introNps;
74
//        private Boolean isOrcidLinked;
75
//        private String orcidLinkError;
76

77
    private Integer localIntroCount = null;
3✔
78
    private IntroNanopub localIntro = null;
3✔
79

80
    private Date lastTimeIntroPublished = null;
3✔
81

82
    // We should store here some sort of form model and not the forms themselves, but I couldn't figure
83
    // how to do it, so doing it like this for the moment...
84
    private ConcurrentMap<String, PublishForm> formMap = new ConcurrentHashMap<>();
5✔
85

86
    /**
87
     * Associates a form object with a specific ID.
88
     *
89
     * @param formObjId The ID of the form object.
90
     * @param formObj   The form object to associate.
91
     */
92
    public void setForm(String formObjId, PublishForm formObj) {
93
        formMap.put(formObjId, formObj);
×
94
    }
×
95

96
    /**
97
     * Checks if a form object with the given ID exists.
98
     *
99
     * @param formObjId The ID of the form object.
100
     * @return True if the form object exists, false otherwise.
101
     */
102
    public boolean hasForm(String formObjId) {
103
        return formMap.containsKey(formObjId);
×
104
    }
105

106
    /**
107
     * Retrieves the form object associated with the given ID.
108
     *
109
     * @param formObjId The ID of the form object.
110
     * @return The associated form object, or null if not found.
111
     */
112
    public PublishForm getForm(String formObjId) {
113
        return formMap.get(formObjId);
×
114
    }
115

116
    /**
117
     * Loads profile information for the user.
118
     * Initializes user-related data such as keys and introductions.
119
     */
120
    public void loadProfileInfo() {
121
        localIntroCount = null;
3✔
122
        localIntro = null;
3✔
123
        NanodashPreferences prefs = NanodashPreferences.get();
2✔
124
        if (prefs.isOrcidLoginMode()) {
3!
125
            File usersDir = new File(System.getProperty("user.home") + "/.nanopub/nanodash-users/");
×
126
            if (!usersDir.exists()) usersDir.mkdir();
×
127
        }
128
        if (userIri == null && !prefs.isReadOnlyMode() && !prefs.isOrcidLoginMode()) {
9!
129
            if (getOrcidFile().exists()) {
4!
130
                try {
131
                    String orcid = FileUtils.readFileToString(getOrcidFile(), StandardCharsets.UTF_8).trim();
×
132
                    //String orcid = Files.readString(orcidFile.toPath(), StandardCharsets.UTF_8).trim();
133
                    if (orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
134
                        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
135
                        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
136
                    }
137
                } catch (IOException ex) {
×
138
                    logger.error("Couldn't read ORCID file", ex);
×
139
                }
×
140
            }
141
        }
142
        if (userIri != null && keyPair == null) {
3!
143
            File keyFile = getKeyFile();
×
144
            if (keyFile.exists()) {
×
145
                try {
146
                    keyPair = SignNanopub.loadKey(keyFile.getPath(), SignatureAlgorithm.RSA);
×
147
                } catch (Exception ex) {
×
148
                    logger.error("Couldn't load key pair", ex);
×
149
                }
×
150
            } else {
151
                // Automatically generate new keys
152
                makeKeys();
×
153
            }
154
        }
155
        if (userIri != null && keyPair != null && introNps == null) {
3!
156
            introNps = new ConcurrentHashMap<>(User.getIntroNanopubs(getPubkeyString()));
×
157
        }
158
//                checkOrcidLink();
159
    }
1✔
160

161
    /**
162
     * Checks if the user's profile is complete.
163
     *
164
     * @return True if the profile is complete, false otherwise.
165
     */
166
    public boolean isProfileComplete() {
167
        return userIri != null && keyPair != null && introNps != null;
×
168
    }
169

170
    /**
171
     * Redirects the user to the login page if their profile is incomplete.
172
     *
173
     * @param path       The path to redirect to after login.
174
     * @param parameters The page parameters for the redirect.
175
     */
176
    public void redirectToLoginIfNeeded(String path, PageParameters parameters) {
177
        String loginUrl = getLoginUrl(path, parameters);
×
178
        if (loginUrl == null) return;
×
179
        throw new RedirectToUrlException(loginUrl);
×
180
    }
181

182
    /**
183
     * Retrieves the login URL for the user.
184
     *
185
     * @param path       The path to redirect to after login.
186
     * @param parameters The page parameters for the redirect.
187
     * @return The login URL, or null if the user is already logged in.
188
     */
189
    public String getLoginUrl(String path, PageParameters parameters) {
190
        if (isProfileComplete()) return null;
×
191
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
192
            return OrcidLoginPage.getOrcidLoginUrl(path, parameters);
×
193
        } else {
194
            return ProfilePage.MOUNT_PATH;
×
195
        }
196
    }
197

198
    /**
199
     * Retrieves the public key as a Base64-encoded string.
200
     *
201
     * @return The public key string, or null if the key pair is not set.
202
     */
203
    public String getPubkeyString() {
204
        if (keyPair == null) return null;
×
205
        return DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded()).replaceAll("\\s", "");
×
206
    }
207

208
    /**
209
     * Retrieves the public key hash for the user.
210
     *
211
     * @return The SHA-256 hash of the public key, or null if the public key is not set.
212
     */
213
    public String getPubkeyhash() {
214
        String pubkey = getPubkeyString();
×
215
        if (pubkey == null) return null;
×
216
        return Utils.createSha256HexHash(pubkey);
×
217
    }
218

219
    /**
220
     * Checks if the user's public key is approved.
221
     *
222
     * @return True if the public key is approved, false otherwise.
223
     */
224
    public boolean isPubkeyApproved() {
225
        if (keyPair == null || userIri == null) return false;
×
226
        return User.isApprovedPubkeyhashForUser(getPubkeyhash(), userIri);
×
227
    }
228

229
    /**
230
     * Retrieves the user's key pair.
231
     *
232
     * @return The key pair.
233
     */
234
    public KeyPair getKeyPair() {
235
        return keyPair;
×
236
    }
237

238
    /**
239
     * Generates a new key pair for the user.
240
     */
241
    public void makeKeys() {
242
        try {
243
            MakeKeys.make(getKeyFile().getAbsolutePath().replaceFirst("_rsa$", ""), SignatureAlgorithm.RSA);
×
244
            keyPair = SignNanopub.loadKey(getKeyFile().getPath(), SignatureAlgorithm.RSA);
×
245
        } catch (Exception ex) {
×
246
            logger.error("Couldn't create key pair", ex);
×
247
        }
×
248
    }
×
249

250
    /**
251
     * Retrieves the user's IRI.
252
     *
253
     * @return The user's IRI, or null if not set.
254
     */
255
    public IRI getUserIri() {
256
        return userIri;
×
257
    }
258

259
    /**
260
     * Retrieves the user's introduction nanopublications.
261
     *
262
     * @return A list of user's introduction nanopublications.
263
     */
264
    public List<IntroNanopub> getUserIntroNanopubs() {
265
        return User.getIntroNanopubs(userIri);
×
266
    }
267

268
    /**
269
     * Counts the number of local introduction nanopublications.
270
     *
271
     * @return The count of local introduction nanopublications.
272
     */
273
    public int getLocalIntroCount() {
274
        if (localIntroCount == null) {
×
275
            localIntroCount = 0;
×
276
            for (IntroNanopub inp : getUserIntroNanopubs()) {
×
277
                if (isIntroWithLocalKey(inp)) {
×
278
                    localIntroCount++;
×
279
                    localIntro = inp;
×
280
                }
281
            }
×
282
            if (localIntroCount > 1) localIntro = null;
×
283
        }
284
        return localIntroCount;
×
285
    }
286

287
    /**
288
     * Retrieves the local introduction nanopublication.
289
     *
290
     * @return The local introduction nanopublication, or null if not found.
291
     */
292
    public IntroNanopub getLocalIntro() {
293
        getLocalIntroCount();
×
294
        return localIntro;
×
295
    }
296

297
    /**
298
     * Checks if the given introduction nanopublication is associated with the local key.
299
     *
300
     * @param inp The introduction nanopublication.
301
     * @return True if associated with the local key, false otherwise.
302
     */
303
    public boolean isIntroWithLocalKey(IntroNanopub inp) {
304
        IRI location = Utils.getLocation(inp);
×
305
        NanopubSignatureElement el = Utils.getNanopubSignatureElement(inp);
×
306
        String siteUrl = NanodashPreferences.get().getWebsiteUrl();
×
307
        if (location != null && siteUrl != null) {
×
308
            String l = location.stringValue();
×
309
            // TODO: Solve the name change recognition in a better way:
310
            if (!l.equals(siteUrl) && !l.replace("nanobench", "nanodash").equals(siteUrl)) return false;
×
311
        }
312
        if (!getPubkeyString().equals(el.getPublicKeyString())) return false;
×
313
        for (KeyDeclaration kd : inp.getKeyDeclarations()) {
×
314
            if (getPubkeyString().equals(kd.getPublicKeyString())) return true;
×
315
        }
×
316
        return false;
×
317
    }
318

319
    /**
320
     * Sets the user's ORCID identifier.
321
     *
322
     * @param orcid The ORCID identifier.
323
     */
324
    public void setOrcid(String orcid) {
325
        if (!orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
326
            throw new RuntimeException("Illegal ORCID identifier: " + orcid);
×
327
        }
328
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
329
            userDir = System.getProperty("user.home") + "/.nanopub/nanodash-users/" + orcid + "/";
×
330
            File f = new File(userDir);
×
331
            if (!f.exists()) f.mkdir();
×
332
        } else {
×
333
            try {
334
                FileUtils.writeStringToFile(getOrcidFile(), orcid + "\n", StandardCharsets.UTF_8);
×
335
                //                        Files.writeString(orcidFile.toPath(), orcid + "\n");
336
            } catch (IOException ex) {
×
337
                logger.error("Couldn't write ORCID file", ex);
×
338
            }
×
339
        }
340
        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
341
        loadProfileInfo();
×
342
        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
343
    }
×
344

345
    /**
346
     * Logs out the user and invalidates the session.
347
     */
348
    public void logout() {
349
        userIri = null;
×
350
        invalidateNow();
×
351
    }
×
352

353
    /**
354
     * Retrieves the user's introduction nanopublications as a map.
355
     *
356
     * @return A map of introduction nanopublications.
357
     */
358
    public Map<IRI, IntroNanopub> getIntroNanopubs() {
359
        return introNps;
×
360
    }
361

362
//        public void checkOrcidLink() {
363
//                if (isOrcidLinked == null && userIri != null) {
364
//                        orcidLinkError = "";
365
//                        introExtractor = null;
366
//                        try {
367
//                                introExtractor = IntroNanopub.extract(userIri.stringValue(), null);
368
//                                if (introExtractor.getIntroNanopub() == null) {
369
//                                        orcidLinkError = "ORCID account is not linked.";
370
//                                        isOrcidLinked = false;
371
//                                } else {
372
//                                        IntroNanopub inp = IntroNanopub.get(userIri.stringValue(), introExtractor);
373
//                                        if (introNps != null && introNps.containsKey(inp.getNanopub().getUri())) {
374
//                                                // TODO: also check whether introduction contains local key
375
//                                                isOrcidLinked = true;
376
//                                        } else {
377
//                                                isOrcidLinked = false;
378
//                                                orcidLinkError = "Error: ORCID is linked to another introduction nanopublication.";
379
//                                        }
380
//                                }
381
//                        } catch (Exception ex) {
382
//                                logger.error("ORCID check failed");
383
//                                orcidLinkError = "ORCID check failed.";
384
//                        }
385
//                }
386
//        }
387
//
388
//        public void resetOrcidLinked() {
389
//                isOrcidLinked = null;
390
//        }
391
//
392
//        public boolean isOrcidLinked() {
393
//                checkOrcidLink();
394
//                return isOrcidLinked != null && isOrcidLinked == true;
395
//        }
396
//
397
//        public String getOrcidLinkError() {
398
//                return orcidLinkError;
399
//        }
400
//
401
//        public String getOrcidName() {
402
//                if (introExtractor == null || introExtractor.getName() == null) return null;
403
//                if (introExtractor.getName().trim().isEmpty()) return null;
404
//                return introExtractor.getName();
405
//        }
406

407
    /**
408
     * Retrieves the file for storing the user's ORCID identifier.
409
     *
410
     * @return The ORCID file.
411
     */
412
    private File getOrcidFile() {
413
        return new File(userDir + "orcid");
7✔
414
    }
415

416
    /**
417
     * Retrieves the file for storing the user's private key.
418
     *
419
     * @return The key file.
420
     */
421
    public File getKeyFile() {
422
        return new File(userDir + "id_rsa");
×
423
    }
424

425
    /**
426
     * Sets the time when the introduction was last published.
427
     */
428
    public void setIntroPublishedNow() {
429
        lastTimeIntroPublished = new Date();
×
430
    }
×
431

432
    /**
433
     * Checks if the introduction has been published.
434
     *
435
     * @return True if the introduction has been published, false otherwise.
436
     */
437
    public boolean hasIntroPublished() {
438
        return lastTimeIntroPublished != null;
×
439
    }
440

441
    /**
442
     * Calculates the time since the last introduction was published.
443
     *
444
     * @return The time in milliseconds since the last introduction was published, or Long.MAX_VALUE if it has never been published.
445
     */
446
    public long getTimeSinceLastIntroPublished() {
447
        if (lastTimeIntroPublished == null) return Long.MAX_VALUE;
×
448
        return new Date().getTime() - lastTimeIntroPublished.getTime();
×
449
    }
450

451
    /**
452
     * Sets the view mode for displaying nanopublication results.
453
     *
454
     * @param viewMode The desired view mode (e.g., GRID or LIST).
455
     */
456
    public void setNanopubResultsViewMode(NanopubResults.ViewMode viewMode) {
457
        this.nanopubResultsViewMode = viewMode;
×
458
    }
×
459

460
    /**
461
     * Retrieves the current view mode for displaying nanopublication results.
462
     *
463
     * @return The current view mode.
464
     */
465
    public NanopubResults.ViewMode getNanopubResultsViewMode() {
466
        return this.nanopubResultsViewMode;
×
467
    }
468

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