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

knowledgepixels / nanodash / 17380144000

01 Sep 2025 02:12PM UTC coverage: 12.03% (+0.05%) from 11.978%
17380144000

push

github

ashleycaselli
refactor: replace printStackTrace with logger.error for better error handling

330 of 3850 branches covered (8.57%)

Branch coverage included in aggregate %.

958 of 6857 relevant lines covered (13.97%)

0.62 hits per line

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

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

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

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

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

38
    private static final long serialVersionUID = -7920814788717089213L;
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

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

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

79
    private Date lastTimeIntroPublished = null;
3✔
80

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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