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

knowledgepixels / nanodash / 17211438187

25 Aug 2025 02:13PM UTC coverage: 12.474% (+0.03%) from 12.446%
17211438187

push

github

ashleycaselli
build(deps): update org.apache.maven.plugins:maven-compiler-plugin to v3.14.0

330 of 3766 branches covered (8.76%)

Branch coverage included in aggregate %.

982 of 6752 relevant lines covered (14.54%)

0.64 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.HashMap;
29
import java.util.List;
30
import java.util.Map;
31

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

37
    private static final long serialVersionUID = -7920814788717089213L;
38
    private transient HttpSession httpSession;
39
    private static final Logger logger = LoggerFactory.getLogger(NanodashSession.class);
3✔
40

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

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

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

65
//        private IntroExtractor introExtractor;
66

67
    private String userDir = System.getProperty("user.home") + "/.nanopub/";
5✔
68

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

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

78
    private Date lastTimeIntroPublished = null;
3✔
79

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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