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

IQSS / dataverse / #21134

09 Jan 2024 05:37PM CUT coverage: 20.163% (-0.002%) from 20.165%
#21134

Pull #10215

github

web-flow
Merge dfb1795e1 into 17bfeb96c
Pull Request #10215: 10202 extend get version files api to include total file count

16638 of 82516 relevant lines covered (20.16%)

0.2 hits per line

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

0.0
/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
1
package edu.harvard.iq.dataverse.api;
2

3
import edu.harvard.iq.dataverse.BannerMessage;
4
import edu.harvard.iq.dataverse.BannerMessageServiceBean;
5
import edu.harvard.iq.dataverse.BannerMessageText;
6
import edu.harvard.iq.dataverse.DataFile;
7
import edu.harvard.iq.dataverse.DataFileServiceBean;
8
import edu.harvard.iq.dataverse.Dataset;
9
import edu.harvard.iq.dataverse.DatasetServiceBean;
10
import edu.harvard.iq.dataverse.DatasetVersion;
11
import edu.harvard.iq.dataverse.DatasetVersionServiceBean;
12
import edu.harvard.iq.dataverse.Dataverse;
13
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
14
import edu.harvard.iq.dataverse.DataverseServiceBean;
15
import edu.harvard.iq.dataverse.DataverseSession;
16
import edu.harvard.iq.dataverse.DvObject;
17
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
18
import edu.harvard.iq.dataverse.settings.JvmSettings;
19
import edu.harvard.iq.dataverse.validation.EMailValidator;
20
import edu.harvard.iq.dataverse.EjbDataverseEngine;
21
import edu.harvard.iq.dataverse.HandlenetServiceBean;
22
import edu.harvard.iq.dataverse.Template;
23
import edu.harvard.iq.dataverse.TemplateServiceBean;
24
import edu.harvard.iq.dataverse.UserServiceBean;
25
import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord;
26
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
27
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;
28
import edu.harvard.iq.dataverse.authorization.AuthenticationProvider;
29
import edu.harvard.iq.dataverse.authorization.UserIdentifier;
30
import edu.harvard.iq.dataverse.authorization.exceptions.AuthenticationProviderFactoryNotFoundException;
31
import edu.harvard.iq.dataverse.authorization.exceptions.AuthorizationSetupException;
32
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
33
import edu.harvard.iq.dataverse.authorization.providers.AuthenticationProviderRow;
34
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUser;
35
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
36
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
37
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibServiceBean;
38
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibUtil;
39
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
40
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
41
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData;
42
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException;
43
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailInitResponse;
44
import edu.harvard.iq.dataverse.dataaccess.DataAccess;
45
import edu.harvard.iq.dataverse.dataaccess.DataAccessOption;
46
import edu.harvard.iq.dataverse.dataaccess.StorageIO;
47
import edu.harvard.iq.dataverse.engine.command.impl.AbstractSubmitToArchiveCommand;
48
import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand;
49
import edu.harvard.iq.dataverse.settings.Setting;
50
import jakarta.json.Json;
51
import jakarta.json.JsonArrayBuilder;
52
import jakarta.json.JsonObjectBuilder;
53
import jakarta.ws.rs.Consumes;
54
import jakarta.ws.rs.DELETE;
55
import jakarta.ws.rs.GET;
56
import jakarta.ws.rs.POST;
57
import jakarta.ws.rs.PUT;
58
import jakarta.ws.rs.Path;
59
import jakarta.ws.rs.PathParam;
60
import jakarta.ws.rs.container.ContainerRequestContext;
61
import jakarta.ws.rs.core.Context;
62
import jakarta.ws.rs.core.Response;
63
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
64

65
import java.io.InputStream;
66
import java.io.StringReader;
67
import java.util.Map;
68
import java.util.Map.Entry;
69
import java.util.logging.Level;
70
import java.util.logging.Logger;
71
import jakarta.ejb.EJB;
72
import jakarta.ejb.Stateless;
73
import jakarta.json.JsonObject;
74
import jakarta.json.JsonReader;
75
import jakarta.validation.ConstraintViolation;
76
import jakarta.validation.ConstraintViolationException;
77
import jakarta.ws.rs.Produces;
78
import jakarta.ws.rs.core.Response.Status;
79

80
import org.apache.commons.io.IOUtils;
81

82
import java.util.List;
83
import edu.harvard.iq.dataverse.authorization.AuthTestDataServiceBean;
84
import edu.harvard.iq.dataverse.authorization.AuthenticationProvidersRegistrationServiceBean;
85
import edu.harvard.iq.dataverse.authorization.DataverseRole;
86
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
87
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
88
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean;
89
import edu.harvard.iq.dataverse.authorization.users.User;
90
import edu.harvard.iq.dataverse.dataaccess.ImageThumbConverter;
91
import edu.harvard.iq.dataverse.dataset.DatasetThumbnail;
92
import edu.harvard.iq.dataverse.dataset.DatasetUtil;
93
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
94
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
95
import edu.harvard.iq.dataverse.engine.command.impl.DeactivateUserCommand;
96
import edu.harvard.iq.dataverse.engine.command.impl.DeleteRoleCommand;
97
import edu.harvard.iq.dataverse.engine.command.impl.DeleteTemplateCommand;
98
import edu.harvard.iq.dataverse.engine.command.impl.RegisterDvObjectCommand;
99
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
100
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
101
import edu.harvard.iq.dataverse.userdata.UserListMaker;
102
import edu.harvard.iq.dataverse.userdata.UserListResult;
103
import edu.harvard.iq.dataverse.util.ArchiverUtil;
104
import edu.harvard.iq.dataverse.util.BundleUtil;
105
import edu.harvard.iq.dataverse.util.FileUtil;
106
import edu.harvard.iq.dataverse.util.SystemConfig;
107
import edu.harvard.iq.dataverse.util.URLTokenUtil;
108
import edu.harvard.iq.dataverse.util.UrlSignerUtil;
109

110
import java.io.FileInputStream;
111
import java.io.IOException;
112
import java.io.OutputStream;
113

114
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
115
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.rolesToJson;
116
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.toJsonArray;
117
import java.util.ArrayList;
118
import java.util.Arrays;
119
import java.util.Date;
120
import jakarta.inject.Inject;
121
import jakarta.json.JsonArray;
122
import jakarta.persistence.Query;
123
import jakarta.ws.rs.QueryParam;
124
import jakarta.ws.rs.WebApplicationException;
125
import jakarta.ws.rs.core.StreamingOutput;
126
import java.nio.file.Paths;
127

128
/**
129
 * Where the secure, setup API calls live.
130
 * 
131
 * @author michael
132
 */
133
@Stateless
134
@Path("admin")
135
public class Admin extends AbstractApiBean {
×
136

137
        private static final Logger logger = Logger.getLogger(Admin.class.getName());
×
138

139
        @EJB
140
        AuthenticationProvidersRegistrationServiceBean authProvidersRegistrationSvc;
141
        @EJB
142
        BuiltinUserServiceBean builtinUserService;
143
        @EJB
144
        ShibServiceBean shibService;
145
        @EJB
146
        AuthTestDataServiceBean authTestDataService;
147
        @EJB
148
        UserServiceBean userService;
149
        @EJB
150
        IngestServiceBean ingestService;
151
        @EJB
152
        DataFileServiceBean fileService;
153
        @EJB
154
        DatasetServiceBean datasetService;
155
        @EJB
156
        DataverseServiceBean dataverseService;
157
        @EJB
158
        DatasetVersionServiceBean datasetversionService;
159
        @Inject
160
        DataverseRequestServiceBean dvRequestService;
161
        @EJB
162
        EjbDataverseEngine commandEngine;
163
        @EJB
164
        GroupServiceBean groupService;
165
        @EJB
166
        SettingsServiceBean settingsService;
167
        @EJB
168
        DatasetVersionServiceBean datasetVersionService;
169
        @EJB
170
        ExplicitGroupServiceBean explicitGroupService;
171
        @EJB
172
        BannerMessageServiceBean bannerMessageService;
173
        @EJB
174
        TemplateServiceBean templateService;
175

176
        // Make the session available
177
        @Inject
178
        DataverseSession session;
179

180
        public static final String listUsersPartialAPIPath = "list-users";
181
        public static final String listUsersFullAPIPath = "/api/admin/" + listUsersPartialAPIPath;
182

183
        @Path("settings")
184
        @GET
185
        public Response listAllSettings() {
186
                JsonObjectBuilder bld = jsonObjectBuilder();
×
187
                settingsSvc.listAll().forEach(s -> bld.add(s.getName(), s.getContent()));
×
188
                return ok(bld);
×
189
        }
190

191
        @Path("settings/{name}")
192
        @PUT
193
        public Response putSetting(@PathParam("name") String name, String content) {
194
                Setting s = settingsSvc.set(name, content);
×
195
                return ok(jsonObjectBuilder().add(s.getName(), s.getContent()));
×
196
        }
197

198
        @Path("settings/{name}/lang/{lang}")
199
        @PUT
200
        public Response putSetting(@PathParam("name") String name, @PathParam("lang") String lang, String content) {
201
                Setting s = settingsSvc.set(name, lang, content);
×
202
                return ok("Setting " + name + " - " + lang + " - added.");
×
203
        }
204

205
        @Path("settings/{name}")
206
        @GET
207
        public Response getSetting(@PathParam("name") String name) {
208
                String s = settingsSvc.get(name);
×
209

210
                return (s != null) ? ok(s) : notFound("Setting " + name + " not found");
×
211
        }
212

213
        @Path("settings/{name}")
214
        @DELETE
215
        public Response deleteSetting(@PathParam("name") String name) {
216
                settingsSvc.delete(name);
×
217

218
                return ok("Setting " + name + " deleted.");
×
219
        }
220

221
        @Path("settings/{name}/lang/{lang}")
222
        @DELETE
223
        public Response deleteSetting(@PathParam("name") String name, @PathParam("lang") String lang) {
224
                settingsSvc.delete(name, lang);
×
225
                return ok("Setting " + name + " - " + lang + " deleted.");
×
226
        }
227
        
228
    @Path("template/{id}")
229
    @DELETE
230
    public Response deleteTemplate(@PathParam("id") long id) {
231
        
232
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
233
        if (superuser == null) {
×
234
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find superuser to execute DeleteTemplateCommand.");
×
235
        }
236

237
        Template doomed = templateService.find(id);
×
238
        if (doomed == null) {
×
239
            return error(Response.Status.NOT_FOUND, "Template with id " + id + " -  not found.");
×
240
        }
241

242
        Dataverse dv = doomed.getDataverse();
×
243
        List <Dataverse> dataverseWDefaultTemplate = templateService.findDataversesByDefaultTemplateId(doomed.getId());
×
244

245
        try {
246
            commandEngine.submit(new DeleteTemplateCommand(createDataverseRequest(superuser), dv, doomed, dataverseWDefaultTemplate));
×
247
        } catch (CommandException ex) {
×
248
            Logger.getLogger(Admin.class.getName()).log(Level.SEVERE, null, ex);
×
249
            return error(Response.Status.BAD_REQUEST, ex.getLocalizedMessage());
×
250
        }
×
251

252
        return ok("Template " + doomed.getName() + " deleted.");
×
253
    }
254
    
255
    
256
    @Path("templates")
257
    @GET
258
    public Response findAllTemplates() {
259
        return findTemplates("");
×
260
    }
261
    
262
    @Path("templates/{alias}")
263
    @GET
264
    public Response findTemplates(@PathParam("alias") String alias) {
265
        List<Template> templates;
266

267
            if (alias.isEmpty()) {
×
268
                templates = templateService.findAll();
×
269
            } else {
270
                try{
271
                    Dataverse owner = findDataverseOrDie(alias);
×
272
                    templates = templateService.findByOwnerId(owner.getId());
×
273
                } catch (WrappedResponse r){
×
274
                    return r.getResponse();
×
275
                }
×
276
            }
277

278
            JsonArrayBuilder container = Json.createArrayBuilder();
×
279
            for (Template t : templates) {
×
280
                JsonObjectBuilder bld = Json.createObjectBuilder();
×
281
                bld.add("templateId", t.getId());
×
282
                bld.add("templateName", t.getName());
×
283
                Dataverse loopowner = t.getDataverse();
×
284
                if (loopowner != null) {
×
285
                    bld.add("owner", loopowner.getDisplayName());
×
286
                } else {
287
                    bld.add("owner", "This an orphan template, it may be safely removed");
×
288
                }
289
                container.add(bld);
×
290
            }
×
291

292
            return ok(container);
×
293

294
        
295
    }
296

297
        @Path("authenticationProviderFactories")
298
        @GET
299
        public Response listAuthProviderFactories() {
300
                return ok(authSvc.listProviderFactories().stream()
×
301
                                .map(f -> jsonObjectBuilder().add("alias", f.getAlias()).add("info", f.getInfo()))
×
302
                                .collect(toJsonArray()));
×
303
        }
304

305
        @Path("authenticationProviders")
306
        @GET
307
        public Response listAuthProviders() {
308
                return ok(em.createNamedQuery("AuthenticationProviderRow.findAll", AuthenticationProviderRow.class)
×
309
                                .getResultList().stream().map(r -> json(r)).collect(toJsonArray()));
×
310
        }
311

312
        @Path("authenticationProviders")
313
        @POST
314
        public Response addProvider(AuthenticationProviderRow row) {
315
                try {
316
                        AuthenticationProviderRow managed = em.find(AuthenticationProviderRow.class, row.getId());
×
317
                        if (managed != null) {
×
318
                                managed = em.merge(row);
×
319
                        } else {
320
                                em.persist(row);
×
321
                                managed = row;
×
322
                        }
323
                        if (managed.isEnabled()) {
×
324
                                AuthenticationProvider provider = authProvidersRegistrationSvc.loadProvider(managed);
×
325
                                authProvidersRegistrationSvc.deregisterProvider(provider.getId());
×
326
                                authProvidersRegistrationSvc.registerProvider(provider);
×
327
                        }
328
                        return created("/api/admin/authenticationProviders/" + managed.getId(), json(managed));
×
329
                } catch (AuthorizationSetupException e) {
×
330
                        return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
331
                }
332
        }
333

334
        @Path("authenticationProviders/{id}")
335
        @GET
336
        public Response showProvider(@PathParam("id") String id) {
337
                AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
338
                return (row != null) ? ok(json(row))
×
339
                                : error(Status.NOT_FOUND, "Can't find authetication provider with id '" + id + "'");
×
340
        }
341

342
        @POST
343
        @Path("authenticationProviders/{id}/:enabled")
344
        public Response enableAuthenticationProvider_deprecated(@PathParam("id") String id, String body) {
345
                return enableAuthenticationProvider(id, body);
×
346
        }
347

348
        @PUT
349
        @Path("authenticationProviders/{id}/enabled")
350
        @Produces("application/json")
351
        public Response enableAuthenticationProvider(@PathParam("id") String id, String body) {
352
                body = body.trim();
×
353
                if (!Util.isBoolean(body)) {
×
354
                        return error(Response.Status.BAD_REQUEST, "Illegal value '" + body + "'. Use 'true' or 'false'");
×
355
                }
356
                boolean enable = Util.isTrue(body);
×
357

358
                AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
359
                if (row == null) {
×
360
                        return notFound("Can't find authentication provider with id '" + id + "'");
×
361
                }
362

363
                row.setEnabled(enable);
×
364
                em.merge(row);
×
365

366
                if (enable) {
×
367
                        // enable a provider
368
                        if (authSvc.getAuthenticationProvider(id) != null) {
×
369
                                return ok(String.format("Authentication provider '%s' already enabled", id));
×
370
                        }
371
                        try {
372
                                authProvidersRegistrationSvc.registerProvider(authProvidersRegistrationSvc.loadProvider(row));
×
373
                                return ok(String.format("Authentication Provider %s enabled", row.getId()));
×
374

375
                        } catch (AuthenticationProviderFactoryNotFoundException ex) {
×
376
                                return notFound(String.format("Can't instantiate provider, as there's no factory with alias %s",
×
377
                                                row.getFactoryAlias()));
×
378
                        } catch (AuthorizationSetupException ex) {
×
379
                                logger.log(Level.WARNING, "Error instantiating authentication provider: " + ex.getMessage(), ex);
×
380
                                return error(Status.INTERNAL_SERVER_ERROR,
×
381
                                                String.format("Can't instantiate provider: %s", ex.getMessage()));
×
382
                        }
383

384
                } else {
385
                        // disable a provider
386
                        authProvidersRegistrationSvc.deregisterProvider(id);
×
387
                        return ok("Authentication Provider '" + id + "' disabled. "
×
388
                                        + (authSvc.getAuthenticationProviderIds().isEmpty()
×
389
                                                        ? "WARNING: no enabled authentication providers left."
×
390
                                                        : ""));
×
391
                }
392
        }
393

394
        @GET
395
        @Path("authenticationProviders/{id}/enabled")
396
        public Response checkAuthenticationProviderEnabled(@PathParam("id") String id) {
397
                List<AuthenticationProviderRow> prvs = em
×
398
                                .createNamedQuery("AuthenticationProviderRow.findById", AuthenticationProviderRow.class)
×
399
                                .setParameter("id", id).getResultList();
×
400
                if (prvs.isEmpty()) {
×
401
                        return notFound("Can't find a provider with id '" + id + "'.");
×
402
                } else {
403
                        return ok(Boolean.toString(prvs.get(0).isEnabled()));
×
404
                }
405
        }
406

407
        @DELETE
408
        @Path("authenticationProviders/{id}/")
409
        public Response deleteAuthenticationProvider(@PathParam("id") String id) {
410
                authProvidersRegistrationSvc.deregisterProvider(id);
×
411
                AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
412
                if (row != null) {
×
413
                        em.remove(row);
×
414
                }
415

416
                return ok("AuthenticationProvider " + id + " deleted. "
×
417
                                + (authSvc.getAuthenticationProviderIds().isEmpty()
×
418
                                                ? "WARNING: no enabled authentication providers left."
×
419
                                                : ""));
×
420
        }
421

422
    @GET
423
    @Path("authenticatedUsers/{identifier}/")
424
    public Response getAuthenticatedUserByIdentifier(@PathParam("identifier") String identifier) {
425
        AuthenticatedUser authenticatedUser = authSvc.getAuthenticatedUser(identifier);
×
426
        if (authenticatedUser != null) {
×
427
            return ok(json(authenticatedUser));
×
428
        }
429
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
430
    }
431

432
    @DELETE
433
    @Path("authenticatedUsers/{identifier}/")
434
    public Response deleteAuthenticatedUser(@PathParam("identifier") String identifier) {
435
        AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
436
        if (user != null) {
×
437
            return deleteAuthenticatedUser(user);
×
438
        }
439
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
440
    }
441
    
442
    @DELETE
443
    @Path("authenticatedUsers/id/{id}/")
444
    public Response deleteAuthenticatedUserById(@PathParam("id") Long id) {
445
        AuthenticatedUser user = authSvc.findByID(id);
×
446
        if (user != null) {
×
447
            return deleteAuthenticatedUser(user);
×
448
        }
449
        return error(Response.Status.BAD_REQUEST, "User " + id + " not found.");
×
450
    }
451

452
    private Response deleteAuthenticatedUser(AuthenticatedUser au) {
453
        
454
        //getDeleteUserErrorMessages does all of the tests to see
455
        //if the user is 'deletable' if it returns an empty string the user 
456
        //can be safely deleted.
457
        
458
        String errorMessages = authSvc.getDeleteUserErrorMessages(au);
×
459
        
460
        if (!errorMessages.isEmpty()) {
×
461
            return badRequest(errorMessages);
×
462
        }
463
        
464
        //if the user is deletable we will delete access requests and group membership
465
        // many-to-many relationships that couldn't be cascade deleted
466
        authSvc.removeAuthentictedUserItems(au);
×
467
        
468
        authSvc.deleteAuthenticatedUser(au.getId());
×
469
        return ok("AuthenticatedUser " + au.getIdentifier() + " deleted.");
×
470
    }
471

472
    @POST
473
    @Path("authenticatedUsers/{identifier}/deactivate")
474
    public Response deactivateAuthenticatedUser(@PathParam("identifier") String identifier) {
475
        AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
476
        if (user != null) {
×
477
            return deactivateAuthenticatedUser(user);
×
478
        }
479
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
480
    }
481

482
    @POST
483
    @Path("authenticatedUsers/id/{id}/deactivate")
484
    public Response deactivateAuthenticatedUserById(@PathParam("id") Long id) {
485
        AuthenticatedUser user = authSvc.findByID(id);
×
486
        if (user != null) {
×
487
            return deactivateAuthenticatedUser(user);
×
488
        }
489
        return error(Response.Status.BAD_REQUEST, "User " + id + " not found.");
×
490
    }
491

492
    private Response deactivateAuthenticatedUser(AuthenticatedUser userToDisable) {
493
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
494
        if (superuser == null) {
×
495
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find superuser to execute DeactivateUserCommand.");
×
496
        }
497
        try {
498
            execCommand(new DeactivateUserCommand(createDataverseRequest(superuser), userToDisable));
×
499
            return ok("User " + userToDisable.getIdentifier() + " deactivated.");
×
500
        } catch (WrappedResponse ex) {
×
501
            return ex.getResponse();
×
502
        }
503
    }
504

505
        @POST
506
        @Path("publishDataverseAsCreator/{id}")
507
        public Response publishDataverseAsCreator(@PathParam("id") long id) {
508
                try {
509
                        Dataverse dataverse = dataverseSvc.find(id);
×
510
                        if (dataverse != null) {
×
511
                                AuthenticatedUser authenticatedUser = dataverse.getCreator();
×
512
                                return ok(json(execCommand(
×
513
                                                new PublishDataverseCommand(createDataverseRequest(authenticatedUser), dataverse))));
×
514
                        } else {
515
                                return error(Status.BAD_REQUEST, "Could not find dataverse with id " + id);
×
516
                        }
517
                } catch (WrappedResponse wr) {
×
518
                        return wr.getResponse();
×
519
                }
520
        }
521

522
        @Deprecated
523
        @GET
524
        @AuthRequired
525
        @Path("authenticatedUsers")
526
        public Response listAuthenticatedUsers(@Context ContainerRequestContext crc) {
527
                try {
528
                        AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
529
                        if (!user.isSuperuser()) {
×
530
                                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
531
                        }
532
                } catch (WrappedResponse ex) {
×
533
                        return error(Response.Status.FORBIDDEN, "Superusers only.");
×
534
                }
×
535
                JsonArrayBuilder userArray = Json.createArrayBuilder();
×
536
                authSvc.findAllAuthenticatedUsers().stream().forEach((user) -> {
×
537
                        userArray.add(json(user));
×
538
                });
×
539
                return ok(userArray);
×
540
        }
541

542
        @GET
543
        @AuthRequired
544
        @Path(listUsersPartialAPIPath)
545
        @Produces({ "application/json" })
546
        public Response filterAuthenticatedUsers(
547
                        @Context ContainerRequestContext crc,
548
                        @QueryParam("searchTerm") String searchTerm,
549
                        @QueryParam("selectedPage") Integer selectedPage,
550
                        @QueryParam("itemsPerPage") Integer itemsPerPage,
551
                        @QueryParam("sortKey") String sortKey
552
        ) {
553

554
                User authUser = getRequestUser(crc);
×
555

556
                if (!authUser.isSuperuser()) {
×
557
                        return error(Response.Status.FORBIDDEN,
×
558
                                        BundleUtil.getStringFromBundle("dashboard.list_users.api.auth.not_superuser"));
×
559
                }
560

561
                UserListMaker userListMaker = new UserListMaker(userService);
×
562

563
                // String sortKey = null;
564
                UserListResult userListResult = userListMaker.runUserSearch(searchTerm, itemsPerPage, selectedPage, sortKey);
×
565

566
                return ok(userListResult.toJSON());
×
567
        }
568

569
        /**
570
         * @todo Make this support creation of BuiltInUsers.
571
         *
572
         * @todo Add way more error checking. Only the happy path is tested by AdminIT.
573
         */
574
        @POST
575
        @Path("authenticatedUsers")
576
        public Response createAuthenicatedUser(JsonObject jsonObject) {
577
                logger.fine("JSON in: " + jsonObject);
×
578
                String persistentUserId = jsonObject.getString("persistentUserId");
×
579
                String identifier = jsonObject.getString("identifier");
×
580
                String proposedAuthenticatedUserIdentifier = identifier.replaceFirst("@", "");
×
581
                String firstName = jsonObject.getString("firstName");
×
582
                String lastName = jsonObject.getString("lastName");
×
583
                String emailAddress = jsonObject.getString("email");
×
584
                String position = null;
×
585
                String affiliation = null;
×
586
                UserRecordIdentifier userRecordId = new UserRecordIdentifier(jsonObject.getString("authenticationProviderId"),
×
587
                                persistentUserId);
588
                AuthenticatedUserDisplayInfo userDisplayInfo = new AuthenticatedUserDisplayInfo(firstName, lastName,
×
589
                                emailAddress, affiliation, position);
590
                boolean generateUniqueIdentifier = true;
×
591
                AuthenticatedUser authenticatedUser = authSvc.createAuthenticatedUser(userRecordId,
×
592
                                proposedAuthenticatedUserIdentifier, userDisplayInfo, true);
593
                return ok(json(authenticatedUser));
×
594
        }
595

596
        //TODO: Delete this endpoint after 4.9.3. Was updated with change in docs. --MAD
597
        /**
598
         * curl -X PUT -d "shib@mailinator.com"
599
         * http://localhost:8080/api/admin/authenticatedUsers/id/11/convertShibToBuiltIn
600
         *
601
         * @deprecated We have documented this API endpoint so we'll keep in around for
602
         *             a while but we should encourage everyone to switch to the
603
         *             "convertRemoteToBuiltIn" endpoint and then remove this
604
         *             Shib-specfic one.
605
         */
606
        @PUT
607
        @AuthRequired
608
        @Path("authenticatedUsers/id/{id}/convertShibToBuiltIn")
609
        @Deprecated
610
        public Response convertShibUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
611
                try {
612
                        AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
613
                        if (!user.isSuperuser()) {
×
614
                                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
615
                        }
616
                } catch (WrappedResponse ex) {
×
617
                        return error(Response.Status.FORBIDDEN, "Superusers only.");
×
618
                }
×
619
                try {
620
                        BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
×
621
                        if (builtinUser == null) {
×
622
                                return error(Response.Status.BAD_REQUEST, "User id " + id
×
623
                                                + " could not be converted from Shibboleth to BuiltIn. An Exception was not thrown.");
624
                        }
625
                        AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
×
626
                        JsonObjectBuilder output = Json.createObjectBuilder();
×
627
                        output.add("email", authUser.getEmail());
×
628
                        output.add("username", builtinUser.getUserName());
×
629
                        return ok(output);
×
630
                } catch (Throwable ex) {
×
631
                        StringBuilder sb = new StringBuilder();
×
632
                        sb.append(ex + " ");
×
633
                        while (ex.getCause() != null) {
×
634
                                ex = ex.getCause();
×
635
                                sb.append(ex + " ");
×
636
                        }
637
                        String msg = "User id " + id
×
638
                                        + " could not be converted from Shibboleth to BuiltIn. Details from Exception: " + sb;
639
                        logger.info(msg);
×
640
                        return error(Response.Status.BAD_REQUEST, msg);
×
641
                }
642
        }
643

644
        @PUT
645
        @AuthRequired
646
        @Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn")
647
        public Response convertOAuthUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
648
                try {
649
                        AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
650
                        if (!user.isSuperuser()) {
×
651
                                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
652
                        }
653
                } catch (WrappedResponse ex) {
×
654
                        return error(Response.Status.FORBIDDEN, "Superusers only.");
×
655
                }
×
656
                try {
657
                        BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
×
658
                        //AuthenticatedUser authUser = authService.getAuthenticatedUser(aUser.getUserName());
659
                        if (builtinUser == null) {
×
660
                                return error(Response.Status.BAD_REQUEST, "User id " + id
×
661
                                                + " could not be converted from remote to BuiltIn. An Exception was not thrown.");
662
                        }
663
                        AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
×
664
                        JsonObjectBuilder output = Json.createObjectBuilder();
×
665
                        output.add("email", authUser.getEmail());
×
666
                        output.add("username", builtinUser.getUserName());
×
667
                        return ok(output);
×
668
                } catch (Throwable ex) {
×
669
                        StringBuilder sb = new StringBuilder();
×
670
                        sb.append(ex + " ");
×
671
                        while (ex.getCause() != null) {
×
672
                                ex = ex.getCause();
×
673
                                sb.append(ex + " ");
×
674
                        }
675
                        String msg = "User id " + id + " could not be converted from remote to BuiltIn. Details from Exception: "
×
676
                                        + sb;
677
                        logger.info(msg);
×
678
                        return error(Response.Status.BAD_REQUEST, msg);
×
679
                }
680
        }
681

682
        /**
683
         * This is used in testing via AdminIT.java but we don't expect sysadmins to use
684
         * this.
685
         */
686
        @PUT
687
        @AuthRequired
688
        @Path("authenticatedUsers/convert/builtin2shib")
689
        public Response builtin2shib(@Context ContainerRequestContext crc, String content) {
690
                logger.info("entering builtin2shib...");
×
691
                try {
692
                        AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
×
693
                        if (!userToRunThisMethod.isSuperuser()) {
×
694
                                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
695
                        }
696
                } catch (WrappedResponse ex) {
×
697
                        return error(Response.Status.FORBIDDEN, "Superusers only.");
×
698
                }
×
699
                boolean disabled = false;
×
700
                if (disabled) {
×
701
                        return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
×
702
                }
703
                AuthenticatedUser builtInUserToConvert = null;
×
704
                String emailToFind;
705
                String password;
706
                String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
×
707
                                                                        // tell us their
708
                String newEmailAddressToUse;
709
                try {
710
                        String[] args = content.split(":");
×
711
                        emailToFind = args[0];
×
712
                        password = args[1];
×
713
                        newEmailAddressToUse = args[2];
×
714
                        // authuserId = args[666];
715
                } catch (ArrayIndexOutOfBoundsException ex) {
×
716
                        return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
×
717
                }
×
718
                AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
×
719
                String existing = "NOT FOUND";
×
720
                if (existingAuthUserFoundByEmail != null) {
×
721
                        builtInUserToConvert = existingAuthUserFoundByEmail;
×
722
                        existing = existingAuthUserFoundByEmail.getIdentifier();
×
723
                } else {
724
                        long longToLookup = Long.parseLong(authuserId);
×
725
                        AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
×
726
                        if (specifiedUserToConvert != null) {
×
727
                                builtInUserToConvert = specifiedUserToConvert;
×
728
                        } else {
729
                                return error(Response.Status.BAD_REQUEST,
×
730
                                                "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
731
                                                                + " and no user was found using specified id " + longToLookup);
732
                        }
733
                }
734
                String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
×
735
                Map<String, String> randomUser = authTestDataService.getRandomUser();
×
736
                // String eppn = UUID.randomUUID().toString().substring(0, 8);
737
                String eppn = randomUser.get("eppn");
×
738
                String idPEntityId = randomUser.get("idp");
×
739
                String notUsed = null;
×
740
                String separator = "|";
×
741
                UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(idPEntityId + separator + eppn, notUsed);
×
742
                String overwriteFirstName = randomUser.get("firstName");
×
743
                String overwriteLastName = randomUser.get("lastName");
×
744
                String overwriteEmail = randomUser.get("email");
×
745
                overwriteEmail = newEmailAddressToUse;
×
746
                logger.info("overwriteEmail: " + overwriteEmail);
×
747
                boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
×
748
                if (!validEmail) {
×
749
                        // See https://github.com/IQSS/dataverse/issues/2998
750
                        return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
×
751
                }
752
                /**
753
                 * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
754
                 *       constructor.
755
                 */
756
                /**
757
                 * Here we are exercising (via an API test) shibService.getAffiliation with the
758
                 * TestShib IdP and a non-production DevShibAccountType.
759
                 */
760
                idPEntityId = ShibUtil.testShibIdpEntityId;
×
761
                String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
×
762
                                ShibServiceBean.DevShibAccountType.RANDOM);
763
                logger.info("overwriteAffiliation: " + overwriteAffiliation);
×
764
                /**
765
                 * @todo Find a place to put "position" in the authenticateduser table:
766
                 *       https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
767
                 */
768
                String overwritePosition = "staff;student";
×
769
                AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
×
770
                                overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
771
                JsonObjectBuilder response = Json.createObjectBuilder();
×
772
                JsonArrayBuilder problems = Json.createArrayBuilder();
×
773
                if (password != null) {
×
774
                        response.add("password supplied", password);
×
775
                        boolean knowsExistingPassword = false;
×
776
                        BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
×
777
                        if (oldBuiltInUser != null) {
×
778
                                if (builtInUserToConvert.isDeactivated()) {
×
779
                                        problems.add("builtin account has been deactivated");
×
780
                                        return error(Status.BAD_REQUEST, problems.build().toString());
×
781
                                }
782
                                String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
×
783
                                response.add("old username", usernameOfBuiltinAccountToConvert);
×
784
                                AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
×
785
                                                password);
786
                                if (authenticatedUser != null) {
×
787
                                        knowsExistingPassword = true;
×
788
                                        AuthenticatedUser convertedUser = authSvc.convertBuiltInToShib(builtInUserToConvert, shibProviderId,
×
789
                                                        newUserIdentifierInLookupTable);
790
                                        if (convertedUser != null) {
×
791
                                                /**
792
                                                 * @todo Display name is not being overwritten. Logic must be in Shib backing
793
                                                 *       bean
794
                                                 */
795
                                                AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
×
796
                                                if (updatedInfoUser != null) {
×
797
                                                        response.add("display name overwritten with", updatedInfoUser.getName());
×
798
                                                } else {
799
                                                        problems.add("couldn't update display info");
×
800
                                                }
801
                                        } else {
×
802
                                                problems.add("unable to convert user");
×
803
                                        }
804
                                }
805
                        } else {
×
806
                                problems.add("couldn't find old username");
×
807
                        }
808
                        if (!knowsExistingPassword) {
×
809
                                String message = "User doesn't know password.";
×
810
                                problems.add(message);
×
811
                                /**
812
                                 * @todo Someday we should make a errorResponse method that takes JSON arrays
813
                                 *       and objects.
814
                                 */
815
                                return error(Status.BAD_REQUEST, problems.build().toString());
×
816
                        }
817
                        // response.add("knows existing password", knowsExistingPassword);
818
                }
819

820
                response.add("user to convert", builtInUserToConvert.getIdentifier());
×
821
                response.add("existing user found by email (prompt to convert)", existing);
×
822
                response.add("changing to this provider", shibProviderId);
×
823
                response.add("value to overwrite old first name", overwriteFirstName);
×
824
                response.add("value to overwrite old last name", overwriteLastName);
×
825
                response.add("value to overwrite old email address", overwriteEmail);
×
826
                if (overwriteAffiliation != null) {
×
827
                        response.add("affiliation", overwriteAffiliation);
×
828
                }
829
                response.add("problems", problems);
×
830
                return ok(response);
×
831
        }
832

833
        /**
834
         * This is used in testing via AdminIT.java but we don't expect sysadmins to use
835
         * this.
836
         */
837
        @PUT
838
        @AuthRequired
839
        @Path("authenticatedUsers/convert/builtin2oauth")
840
        public Response builtin2oauth(@Context ContainerRequestContext crc, String content) {
841
                logger.info("entering builtin2oauth...");
×
842
                try {
843
                        AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
×
844
                        if (!userToRunThisMethod.isSuperuser()) {
×
845
                                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
846
                        }
847
                } catch (WrappedResponse ex) {
×
848
                        return error(Response.Status.FORBIDDEN, "Superusers only.");
×
849
                }
×
850
                boolean disabled = false;
×
851
                if (disabled) {
×
852
                        return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
×
853
                }
854
                AuthenticatedUser builtInUserToConvert = null;
×
855
                String emailToFind;
856
                String password;
857
                String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
×
858
                                                                        // tell us their
859
                String newEmailAddressToUse;
860
                String newProviderId;
861
                String newPersistentUserIdInLookupTable;
862
                logger.info("content: " + content);
×
863
                try {
864
                        String[] args = content.split(":");
×
865
                        emailToFind = args[0];
×
866
                        password = args[1];
×
867
                        newEmailAddressToUse = args[2];
×
868
                        newProviderId = args[3];
×
869
                        newPersistentUserIdInLookupTable = args[4];
×
870
                        // authuserId = args[666];
871
                } catch (ArrayIndexOutOfBoundsException ex) {
×
872
                        return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
×
873
                }
×
874
                AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
×
875
                String existing = "NOT FOUND";
×
876
                if (existingAuthUserFoundByEmail != null) {
×
877
                        builtInUserToConvert = existingAuthUserFoundByEmail;
×
878
                        existing = existingAuthUserFoundByEmail.getIdentifier();
×
879
                } else {
880
                        long longToLookup = Long.parseLong(authuserId);
×
881
                        AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
×
882
                        if (specifiedUserToConvert != null) {
×
883
                                builtInUserToConvert = specifiedUserToConvert;
×
884
                        } else {
885
                                return error(Response.Status.BAD_REQUEST,
×
886
                                                "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
887
                                                                + " and no user was found using specified id " + longToLookup);
888
                        }
889
                }
890
                // String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
891
                Map<String, String> randomUser = authTestDataService.getRandomUser();
×
892
                // String eppn = UUID.randomUUID().toString().substring(0, 8);
893
                String eppn = randomUser.get("eppn");
×
894
                String idPEntityId = randomUser.get("idp");
×
895
                String notUsed = null;
×
896
                String separator = "|";
×
897
                // UserIdentifier newUserIdentifierInLookupTable = new
898
                // UserIdentifier(idPEntityId + separator + eppn, notUsed);
899
                UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(newPersistentUserIdInLookupTable, notUsed);
×
900
                String overwriteFirstName = randomUser.get("firstName");
×
901
                String overwriteLastName = randomUser.get("lastName");
×
902
                String overwriteEmail = randomUser.get("email");
×
903
                overwriteEmail = newEmailAddressToUse;
×
904
                logger.info("overwriteEmail: " + overwriteEmail);
×
905
                boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
×
906
                if (!validEmail) {
×
907
                        // See https://github.com/IQSS/dataverse/issues/2998
908
                        return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
×
909
                }
910
                /**
911
                 * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
912
                 *       constructor.
913
                 */
914
                /**
915
                 * Here we are exercising (via an API test) shibService.getAffiliation with the
916
                 * TestShib IdP and a non-production DevShibAccountType.
917
                 */
918
                // idPEntityId = ShibUtil.testShibIdpEntityId;
919
                // String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
920
                // ShibServiceBean.DevShibAccountType.RANDOM);
921
                String overwriteAffiliation = null;
×
922
                logger.info("overwriteAffiliation: " + overwriteAffiliation);
×
923
                /**
924
                 * @todo Find a place to put "position" in the authenticateduser table:
925
                 *       https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
926
                 */
927
                String overwritePosition = "staff;student";
×
928
                AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
×
929
                                overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
930
                JsonObjectBuilder response = Json.createObjectBuilder();
×
931
                JsonArrayBuilder problems = Json.createArrayBuilder();
×
932
                if (password != null) {
×
933
                        response.add("password supplied", password);
×
934
                        boolean knowsExistingPassword = false;
×
935
                        BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
×
936
                        if (oldBuiltInUser != null) {
×
937
                                String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
×
938
                                response.add("old username", usernameOfBuiltinAccountToConvert);
×
939
                                AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
×
940
                                                password);
941
                                if (authenticatedUser != null) {
×
942
                                        knowsExistingPassword = true;
×
943
                                        AuthenticatedUser convertedUser = authSvc.convertBuiltInUserToRemoteUser(builtInUserToConvert,
×
944
                                                        newProviderId, newUserIdentifierInLookupTable);
945
                                        if (convertedUser != null) {
×
946
                                                /**
947
                                                 * @todo Display name is not being overwritten. Logic must be in Shib backing
948
                                                 *       bean
949
                                                 */
950
                                                AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
×
951
                                                if (updatedInfoUser != null) {
×
952
                                                        response.add("display name overwritten with", updatedInfoUser.getName());
×
953
                                                } else {
954
                                                        problems.add("couldn't update display info");
×
955
                                                }
956
                                        } else {
×
957
                                                problems.add("unable to convert user");
×
958
                                        }
959
                                }
960
                        } else {
×
961
                                problems.add("couldn't find old username");
×
962
                        }
963
                        if (!knowsExistingPassword) {
×
964
                                String message = "User doesn't know password.";
×
965
                                problems.add(message);
×
966
                                /**
967
                                 * @todo Someday we should make a errorResponse method that takes JSON arrays
968
                                 *       and objects.
969
                                 */
970
                                return error(Status.BAD_REQUEST, problems.build().toString());
×
971
                        }
972
                        // response.add("knows existing password", knowsExistingPassword);
973
                }
974

975
                response.add("user to convert", builtInUserToConvert.getIdentifier());
×
976
                response.add("existing user found by email (prompt to convert)", existing);
×
977
                response.add("changing to this provider", newProviderId);
×
978
                response.add("value to overwrite old first name", overwriteFirstName);
×
979
                response.add("value to overwrite old last name", overwriteLastName);
×
980
                response.add("value to overwrite old email address", overwriteEmail);
×
981
                if (overwriteAffiliation != null) {
×
982
                        response.add("affiliation", overwriteAffiliation);
×
983
                }
984
                response.add("problems", problems);
×
985
                return ok(response);
×
986
        }
987

988

989

990

991
        @Path("roles")
992
        @POST
993
        public Response createNewBuiltinRole(RoleDTO roleDto) {
994
                ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "createBuiltInRole")
×
995
                                .setInfo(roleDto.getAlias() + ":" + roleDto.getDescription());
×
996
                try {
997
                        return ok(json(rolesSvc.save(roleDto.asRole())));
×
998
                } catch (Exception e) {
×
999
                        alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1000
                        alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1001
                        return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1002
                } finally {
1003
                        actionLogSvc.log(alr);
×
1004
                }
1005
        }
1006

1007
        @Path("roles")
1008
        @GET
1009
        public Response listBuiltinRoles() {
1010
                try {
1011
                        return ok(rolesToJson(rolesSvc.findBuiltinRoles()));
×
1012
                } catch (Exception e) {
×
1013
                        return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1014
                }
1015
        }
1016

1017
    @DELETE
1018
        @AuthRequired
1019
    @Path("roles/{id}")
1020
    public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1021

1022
        return response(req -> {
×
1023
            DataverseRole doomed = findRoleOrDie(id);
×
1024
            execCommand(new DeleteRoleCommand(req, doomed));
×
1025
            return ok("role " + doomed.getName() + " deleted.");
×
1026
        }, getRequestUser(crc));
×
1027
    }
1028

1029
        @Path("superuser/{identifier}")
1030
        @POST
1031
        public Response toggleSuperuser(@PathParam("identifier") String identifier) {
1032
                ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "toggleSuperuser")
×
1033
                                .setInfo(identifier);
×
1034
                try {
1035
                        AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
1036
                        if (user.isDeactivated()) {
×
1037
                            return error(Status.BAD_REQUEST, "You cannot make a deactivated user a superuser.");
×
1038
                        }
1039

1040
                        user.setSuperuser(!user.isSuperuser());
×
1041

1042
                        return ok("User " + user.getIdentifier() + " " + (user.isSuperuser() ? "set" : "removed")
×
1043
                                        + " as a superuser.");
1044
                } catch (Exception e) {
×
1045
                        alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1046
                        alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1047
                        return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1048
                } finally {
1049
                        actionLogSvc.log(alr);
×
1050
                }
1051
        }
1052

1053
    @GET
1054
    @Path("validate/datasets")
1055
    @Produces({"application/json"})
1056
    public Response validateAllDatasets(@QueryParam("variables") boolean includeVariables) {
1057
        
1058
        // Streaming output: the API will start producing 
1059
        // the output right away, as it goes through the list 
1060
        // of the datasets; there's potentially a lot of content 
1061
        // to validate, so we don't want to wait for the process 
1062
        // to finish. Or to wait to encounter the first invalid 
1063
        // object - so we'll be reporting both the success and failure
1064
        // outcomes for all the validated datasets, to give the user
1065
        // an indication of the progress. 
1066
        // This is the first streaming API that produces json that 
1067
        // we have; there may be better ways to stream json - but 
1068
        // what I have put together below works. -- L.A. 
1069
        StreamingOutput stream = new StreamingOutput() {
×
1070

1071
            @Override
1072
            public void write(OutputStream os) throws IOException,
1073
                    WebApplicationException {
1074
                os.write("{\"datasets\": [\n".getBytes());
×
1075
                
1076
                boolean wroteObject = false;
×
1077
                for (Long datasetId : datasetService.findAllLocalDatasetIds()) {
×
1078
                    // Potentially, there's a godzillion datasets in this Dataverse. 
1079
                    // This is why we go through the list of ids here, and instantiate 
1080
                    // only one dataset at a time. 
1081
                    boolean success = false;
×
1082
                    boolean constraintViolationDetected = false;
×
1083
                     
1084
                    JsonObjectBuilder output = Json.createObjectBuilder();
×
1085
                    output.add("datasetId", datasetId);
×
1086

1087
                    
1088
                    try {
1089
                        datasetService.instantiateDatasetInNewTransaction(datasetId, includeVariables);
×
1090
                        success = true;
×
1091
                    } catch (Exception ex) {
×
1092
                        Throwable cause = ex;
×
1093
                        while (cause != null) {
×
1094
                            if (cause instanceof ConstraintViolationException) {
×
1095
                                ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause;
×
1096
                                for (ConstraintViolation<?> constraintViolation : constraintViolationException
×
1097
                                        .getConstraintViolations()) {
×
1098
                                    String databaseRow = constraintViolation.getLeafBean().toString();
×
1099
                                    String field = constraintViolation.getPropertyPath().toString();
×
1100
                                    String invalidValue = null;
×
1101
                                    if (constraintViolation.getInvalidValue() != null) {
×
1102
                                        invalidValue = constraintViolation.getInvalidValue().toString();
×
1103
                                    }
1104
                                    output.add("status", "invalid");
×
1105
                                    output.add("entityClassDatabaseTableRowId", databaseRow);
×
1106
                                    output.add("field", field);
×
1107
                                    output.add("invalidValue", invalidValue == null ? "NULL" : invalidValue);
×
1108
                                    
1109
                                    constraintViolationDetected = true; 
×
1110
                                    
1111
                                    break; 
×
1112
                                    
1113
                                }
1114
                            }
1115
                            cause = cause.getCause();
×
1116
                        }
1117
                    }
×
1118
                    
1119
                    
1120
                    if (success) {
×
1121
                        output.add("status", "valid");
×
1122
                    } else if (!constraintViolationDetected) {
×
1123
                        output.add("status", "unknown");
×
1124
                    }
1125
                    
1126
                    // write it out:
1127
                    
1128
                    if (wroteObject) {
×
1129
                        os.write(",\n".getBytes());
×
1130
                    }
1131

1132
                    os.write(output.build().toString().getBytes("UTF8"));
×
1133
                    
1134
                    if (!wroteObject) {
×
1135
                        wroteObject = true;
×
1136
                    }
1137
                }
×
1138
                
1139
                
1140
                os.write("\n]\n}\n".getBytes());
×
1141
            }
×
1142
            
1143
        };
1144
        return Response.ok(stream).build();
×
1145
    }
1146
        
1147
    @Path("validate/dataset/{id}")
1148
    @GET
1149
    public Response validateDataset(@PathParam("id") String id, @QueryParam("variables") boolean includeVariables) {
1150
        Dataset dataset;
1151
        try {
1152
            dataset = findDatasetOrDie(id);
×
1153
        } catch (Exception ex) {
×
1154
            return error(Response.Status.NOT_FOUND, "No Such Dataset");
×
1155
        }
×
1156

1157
        Long dbId = dataset.getId();
×
1158

1159
        String msg = "unknown";
×
1160
        try {
1161
            datasetService.instantiateDatasetInNewTransaction(dbId, includeVariables);
×
1162
            msg = "valid";
×
1163
        } catch (Exception ex) {
×
1164
            Throwable cause = ex;
×
1165
            while (cause != null) {
×
1166
                if (cause instanceof ConstraintViolationException) {
×
1167
                    ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause;
×
1168
                    for (ConstraintViolation<?> constraintViolation : constraintViolationException
×
1169
                            .getConstraintViolations()) {
×
1170
                        String databaseRow = constraintViolation.getLeafBean().toString();
×
1171
                        String field = constraintViolation.getPropertyPath().toString();
×
1172
                        String invalidValue = null; 
×
1173
                        if (constraintViolation.getInvalidValue() != null) {
×
1174
                            invalidValue = constraintViolation.getInvalidValue().toString();
×
1175
                        }
1176
                        JsonObjectBuilder violation = Json.createObjectBuilder();
×
1177
                        violation.add("entityClassDatabaseTableRowId", databaseRow);
×
1178
                        violation.add("field", field);
×
1179
                        violation.add("invalidValue", invalidValue == null ? "NULL" : invalidValue);
×
1180
                        return ok(violation);
×
1181
                    }
1182
                }
1183
                cause = cause.getCause();
×
1184
            }
1185
        }
×
1186
        return ok(msg);
×
1187
    }
1188
    
1189
    // This API does the same thing as /validateDataFileHashValue/{fileId}, 
1190
    // but for all the files in the dataset, with streaming output.
1191
    @GET
1192
    @Path("validate/dataset/files/{id}")
1193
    @Produces({"application/json"})
1194
    public Response validateDatasetDatafiles(@PathParam("id") String id) {
1195
        
1196
        // Streaming output: the API will start producing 
1197
        // the output right away, as it goes through the list 
1198
        // of the datafiles in the dataset.
1199
        // The streaming mechanism is modeled after validate/datasets API.
1200
        StreamingOutput stream = new StreamingOutput() {
×
1201

1202
            @Override
1203
            public void write(OutputStream os) throws IOException,
1204
                    WebApplicationException {
1205
                Dataset dataset;
1206
        
1207
                try {
1208
                    dataset = findDatasetOrDie(id);
×
1209
                } catch (Exception ex) {
×
1210
                    throw new IOException(ex.getMessage());
×
1211
                }
×
1212
                
1213
                os.write("{\"dataFiles\": [\n".getBytes());
×
1214
                
1215
                boolean wroteObject = false;
×
1216
                for (DataFile dataFile : dataset.getFiles()) {
×
1217
                    // Potentially, there's a godzillion datasets in this Dataverse. 
1218
                    // This is why we go through the list of ids here, and instantiate 
1219
                    // only one dataset at a time. 
1220
                    boolean success = false;
×
1221
                    boolean constraintViolationDetected = false;
×
1222
                     
1223
                    JsonObjectBuilder output = Json.createObjectBuilder();
×
1224
                    output.add("datafileId", dataFile.getId());
×
1225
                    output.add("storageIdentifier", dataFile.getStorageIdentifier());
×
1226

1227
                    
1228
                    try {
1229
                        FileUtil.validateDataFileChecksum(dataFile);
×
1230
                        success = true;
×
1231
                    } catch (IOException ex) {
×
1232
                        output.add("status", "invalid");
×
1233
                        output.add("errorMessage", ex.getMessage());
×
1234
                    }
×
1235
                    
1236
                    if (success) {
×
1237
                        output.add("status", "valid");
×
1238
                    } 
1239
                    
1240
                    // write it out:
1241
                    
1242
                    if (wroteObject) {
×
1243
                        os.write(",\n".getBytes());
×
1244
                    }
1245

1246
                    os.write(output.build().toString().getBytes("UTF8"));
×
1247
                    
1248
                    if (!wroteObject) {
×
1249
                        wroteObject = true;
×
1250
                    }
1251
                }
×
1252
                
1253
                os.write("\n]\n}\n".getBytes());
×
1254
            }
×
1255
            
1256
        };
1257
        return Response.ok(stream).build();
×
1258
    }
1259

1260
        @Path("assignments/assignees/{raIdtf: .*}")
1261
        @GET
1262
        public Response getAssignmentsFor(@PathParam("raIdtf") String raIdtf) {
1263

1264
                JsonArrayBuilder arr = Json.createArrayBuilder();
×
1265
                roleAssigneeSvc.getAssignmentsFor(raIdtf).forEach(a -> arr.add(json(a)));
×
1266

1267
                return ok(arr);
×
1268
        }
1269

1270
        /**
1271
         * This method is used in integration tests.
1272
         *
1273
         * @param userId
1274
         *            The database id of an AuthenticatedUser.
1275
         * @return The confirm email token.
1276
         */
1277
        @Path("confirmEmail/{userId}")
1278
        @GET
1279
        public Response getConfirmEmailToken(@PathParam("userId") long userId) {
1280
                AuthenticatedUser user = authSvc.findByID(userId);
×
1281
                if (user != null) {
×
1282
                        ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user);
×
1283
                        if (confirmEmailData != null) {
×
1284
                                return ok(Json.createObjectBuilder().add("token", confirmEmailData.getToken()));
×
1285
                        }
1286
                }
1287
                return error(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId);
×
1288
        }
1289

1290
        /**
1291
         * This method is used in integration tests.
1292
         *
1293
         * @param userId
1294
         *            The database id of an AuthenticatedUser.
1295
         */
1296
        @Path("confirmEmail/{userId}")
1297
        @POST
1298
        public Response startConfirmEmailProcess(@PathParam("userId") long userId) {
1299
                AuthenticatedUser user = authSvc.findByID(userId);
×
1300
                if (user != null) {
×
1301
                        try {
1302
                                ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user);
×
1303
                                ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData();
×
1304
                                return ok(Json.createObjectBuilder().add("tokenCreated", confirmEmailData.getCreated().toString())
×
1305
                                                .add("identifier", user.getUserIdentifier()));
×
1306
                        } catch (ConfirmEmailException ex) {
×
1307
                                return error(Status.BAD_REQUEST,
×
1308
                                                "Could not start confirm email process for user " + userId + ": " + ex.getLocalizedMessage());
×
1309
                        }
1310
                }
1311
                return error(Status.BAD_REQUEST, "Could not find user based on " + userId);
×
1312
        }
1313

1314
        /**
1315
         * This method is used by an integration test in UsersIT.java to exercise bug
1316
         * https://github.com/IQSS/dataverse/issues/3287 . Not for use by users!
1317
         */
1318
        @Path("convertUserFromBcryptToSha1")
1319
        @POST
1320
        public Response convertUserFromBcryptToSha1(String json) {
1321
                JsonReader jsonReader = Json.createReader(new StringReader(json));
×
1322
                JsonObject object = jsonReader.readObject();
×
1323
                jsonReader.close();
×
1324
                BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
×
1325
                builtinUser.updateEncryptedPassword("4G7xxL9z11/JKN4jHPn4g9iIQck=", 0); // password is "sha-1Pass", 0 means
×
1326
                                                                                                                                                                // SHA-1
1327
                BuiltinUser savedUser = builtinUserService.save(builtinUser);
×
1328
                return ok("foo: " + savedUser);
×
1329

1330
        }
1331

1332
        @Path("permissions/{dvo}")
1333
        @AuthRequired
1334
        @GET
1335
        public Response findPermissonsOn(@Context ContainerRequestContext crc, @PathParam("dvo") String dvo) {
1336
                try {
1337
                        DvObject dvObj = findDvo(dvo);
×
1338
                        if (dvObj == null) {
×
1339
                                return notFound("DvObject " + dvo + " not found");
×
1340
                        }
1341
                        User aUser = getRequestUser(crc);
×
1342
                        JsonObjectBuilder bld = Json.createObjectBuilder();
×
1343
                        bld.add("user", aUser.getIdentifier());
×
1344
                        bld.add("permissions", json(permissionSvc.permissionsFor(createDataverseRequest(aUser), dvObj)));
×
1345
                        return ok(bld);
×
1346

1347
                } catch (Exception e) {
×
1348
                        logger.log(Level.SEVERE, "Error while testing permissions", e);
×
1349
                        return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1350
                }
1351
        }
1352

1353
        @Path("assignee/{idtf}")
1354
        @GET
1355
        public Response findRoleAssignee(@PathParam("idtf") String idtf) {
1356
                RoleAssignee ra = roleAssigneeSvc.getRoleAssignee(idtf);
×
1357
                return (ra == null) ? notFound("Role Assignee '" + idtf + "' not found.") : ok(json(ra.getDisplayInfo()));
×
1358
        }
1359

1360
        @Path("datasets/integrity/{datasetVersionId}/fixmissingunf")
1361
        @POST
1362
        public Response fixUnf(@PathParam("datasetVersionId") String datasetVersionId,
1363
                        @QueryParam("forceRecalculate") boolean forceRecalculate) {
1364
                JsonObjectBuilder info = datasetVersionSvc.fixMissingUnf(datasetVersionId, forceRecalculate);
×
1365
                return ok(info);
×
1366
        }
1367

1368
        @Path("datafiles/integrity/fixmissingoriginaltypes")
1369
        @GET
1370
        public Response fixMissingOriginalTypes() {
1371
                JsonObjectBuilder info = Json.createObjectBuilder();
×
1372

1373
                List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalTypes();
×
1374

1375
                if (affectedFileIds.isEmpty()) {
×
1376
                        info.add("message",
×
1377
                                        "All the tabular files in the database already have the original types set correctly; exiting.");
1378
                } else {
1379
                        for (Long fileid : affectedFileIds) {
×
1380
                                logger.fine("found file id: " + fileid);
×
1381
                        }
×
1382
                        info.add("message", "Found " + affectedFileIds.size()
×
1383
                                        + " tabular files with missing original types. Kicking off an async job that will repair the files in the background.");
1384
                }
1385

1386
                ingestService.fixMissingOriginalTypes(affectedFileIds);
×
1387

1388
                return ok(info);
×
1389
        }
1390
        
1391
    @Path("datafiles/integrity/fixmissingoriginalsizes")
1392
    @GET
1393
    public Response fixMissingOriginalSizes(@QueryParam("limit") Integer limit) {
1394
        JsonObjectBuilder info = Json.createObjectBuilder();
×
1395

1396
        List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalSizes();
×
1397

1398
        if (affectedFileIds.isEmpty()) {
×
1399
            info.add("message",
×
1400
                    "All the tabular files in the database already have the original sizes set correctly; exiting.");
1401
        } else {
1402
            
1403
            int howmany = affectedFileIds.size();
×
1404
            String message = "Found " + howmany + " tabular files with missing original sizes. "; 
×
1405
            
1406
            if (limit == null || howmany <= limit) {
×
1407
                message = message.concat(" Kicking off an async job that will repair the files in the background.");
×
1408
            } else {
1409
                affectedFileIds.subList(limit, howmany-1).clear();
×
1410
                message = message.concat(" Kicking off an async job that will repair the " + limit + " files in the background.");                        
×
1411
            }
1412
            info.add("message", message);
×
1413
        }
1414

1415
        ingestService.fixMissingOriginalSizes(affectedFileIds);
×
1416
        return ok(info);
×
1417
    }
1418

1419
        /**
1420
         * This method is used in API tests, called from UtilIt.java.
1421
         */
1422
        @GET
1423
        @Path("datasets/thumbnailMetadata/{id}")
1424
        public Response getDatasetThumbnailMetadata(@PathParam("id") Long idSupplied) {
1425
                Dataset dataset = datasetSvc.find(idSupplied);
×
1426
                if (dataset == null) {
×
1427
                        return error(Response.Status.NOT_FOUND, "Could not find dataset based on id supplied: " + idSupplied + ".");
×
1428
                }
1429
                JsonObjectBuilder data = Json.createObjectBuilder();
×
1430
                DatasetThumbnail datasetThumbnail = dataset.getDatasetThumbnail(ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE);
×
1431
                data.add("isUseGenericThumbnail", dataset.isUseGenericThumbnail());
×
1432
                data.add("datasetLogoPresent", DatasetUtil.isDatasetLogoPresent(dataset, ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE));
×
1433
                if (datasetThumbnail != null) {
×
1434
                        data.add("datasetThumbnailBase64image", datasetThumbnail.getBase64image());
×
1435
                        DataFile dataFile = datasetThumbnail.getDataFile();
×
1436
                        if (dataFile != null) {
×
1437
                                /**
1438
                                 * @todo Change this from a String to a long.
1439
                                 */
1440
                                data.add("dataFileId", dataFile.getId().toString());
×
1441
                        }
1442
                }
1443
                return ok(data);
×
1444
        }
1445

1446
        /**
1447
         * validatePassword
1448
         * <p>
1449
         * Validate a password with an API call
1450
         *
1451
         * @param password
1452
         *            The password
1453
         * @return A response with the validation result.
1454
         */
1455
        @Path("validatePassword")
1456
        @POST
1457
        public Response validatePassword(String password) {
1458

1459
                final List<String> errors = passwordValidatorService.validate(password, new Date(), false);
×
1460
                final JsonArrayBuilder errorArray = Json.createArrayBuilder();
×
1461
                errors.forEach(errorArray::add);
×
1462
                return ok(Json.createObjectBuilder().add("password", password).add("errors", errorArray));
×
1463
        }
1464

1465
        @GET
1466
        @Path("/isOrcid")
1467
        public Response isOrcidEnabled() {
1468
                return authSvc.isOrcidEnabled() ? ok("Orcid is enabled") : ok("no orcid for you.");
×
1469
        }
1470

1471
    @POST
1472
        @AuthRequired
1473
    @Path("{id}/reregisterHDLToPID")
1474
    public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1475
        logger.info("Starting to reregister  " + id + " Dataset Id. (from hdl to doi)" + new Date());
×
1476
        try {
1477
            if (settingsSvc.get(SettingsServiceBean.Key.Protocol.toString()).equals(HandlenetServiceBean.HDL_PROTOCOL)) {
×
1478
                logger.info("Bad Request protocol set to handle  " );
×
1479
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.set.for.doi"));
×
1480
            }
1481
            
1482
            User u = getRequestUser(crc);
×
1483
            if (!u.isSuperuser()) {
×
1484
                logger.info("Bad Request Unauthor " );
×
1485
                return error(Status.UNAUTHORIZED, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser"));
×
1486
            }
1487

1488
            DataverseRequest r = createDataverseRequest(u);
×
1489
            Dataset ds = findDatasetOrDie(id);
×
1490
            if (ds.getIdentifier() != null && !ds.getIdentifier().isEmpty() && ds.getProtocol().equals(HandlenetServiceBean.HDL_PROTOCOL)) {
×
1491
                execCommand(new RegisterDvObjectCommand(r, ds, true));
×
1492
            } else {
1493
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.hdl.dataset"));
×
1494
            }
1495

1496
        } catch (WrappedResponse r) {
×
1497
            logger.info("Failed to migrate Dataset Handle id: " + id);
×
1498
            return badRequest(BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure", Arrays.asList(id)));
×
1499
        } catch (Exception e) {
×
1500
            logger.info("Failed to migrate Dataset Handle id: " + id + " Unexpected Exception " + e.getMessage());
×
1501
            List<String> args = Arrays.asList(id,e.getMessage());
×
1502
            return badRequest(BundleUtil.getStringFromBundle("admin.api.migrateHDL.failureWithException", args));
×
1503
        }
×
1504
        
1505
        return ok(BundleUtil.getStringFromBundle("admin.api.migrateHDL.success"));
×
1506
    }
1507

1508
    @GET
1509
    @AuthRequired
1510
    @Path("{id}/registerDataFile")
1511
    public Response registerDataFile(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1512
        logger.info("Starting to register  " + id + " file id. " + new Date());
×
1513

1514
        try {
1515
            User u = getRequestUser(crc);
×
1516
            DataverseRequest r = createDataverseRequest(u);
×
1517
            DataFile df = findDataFileOrDie(id);
×
1518
            if(!systemConfig.isFilePIDsEnabledForCollection(df.getOwner().getOwner())) {
×
1519
                return forbidden("PIDs are not enabled for this file's collection.");
×
1520
            }
1521
            if (df.getIdentifier() == null || df.getIdentifier().isEmpty()) {
×
1522
                execCommand(new RegisterDvObjectCommand(r, df));
×
1523
            } else {
1524
                return ok("File was already registered. ");
×
1525
            }
1526

1527
        } catch (WrappedResponse r) {
×
1528
            logger.info("Failed to register file id: " + id);
×
1529
        } catch (Exception e) {
×
1530
            logger.info("Failed to register file id: " + id + " Unexpecgted Exception " + e.getMessage());
×
1531
        }
×
1532
        return ok("Datafile registration complete. File registered successfully.");
×
1533
    }
1534

1535
    @GET
1536
    @AuthRequired
1537
    @Path("/registerDataFileAll")
1538
    public Response registerDataFileAll(@Context ContainerRequestContext crc) {
1539
        Integer count = fileService.findAll().size();
×
1540
        Integer successes = 0;
×
1541
        Integer alreadyRegistered = 0;
×
1542
        Integer released = 0;
×
1543
        Integer draft = 0;
×
1544
        Integer skipped = 0;
×
1545
        logger.info("Starting to register: analyzing " + count + " files. " + new Date());
×
1546
        logger.info("Only unregistered, published files will be registered.");
×
1547
        User u = null;
×
1548
        try {
1549
            u = getRequestAuthenticatedUserOrDie(crc);
×
1550
        } catch (WrappedResponse e1) {
×
1551
            return error(Status.UNAUTHORIZED, "api key required");
×
1552
        }
×
1553
        DataverseRequest r = createDataverseRequest(u);
×
1554
        for (DataFile df : fileService.findAll()) {
×
1555
            try {
1556
                if ((df.getIdentifier() == null || df.getIdentifier().isEmpty())) {
×
1557
                    if(!systemConfig.isFilePIDsEnabledForCollection(df.getOwner().getOwner())) {
×
1558
                        skipped++;
×
1559
                        if (skipped % 100 == 0) {
×
1560
                            logger.info(skipped + " of  " + count + " files not in collections that allow file PIDs. " + new Date());
×
1561
                        }
1562
                    } else if (df.isReleased()) {
×
1563
                        released++;
×
1564
                        execCommand(new RegisterDvObjectCommand(r, df));
×
1565
                        successes++;
×
1566
                        if (successes % 100 == 0) {
×
1567
                            logger.info(successes + " of  " + count + " files registered successfully. " + new Date());
×
1568
                        }
1569
                        try {
1570
                            Thread.sleep(1000);
×
1571
                        } catch (InterruptedException ie) {
×
1572
                            logger.warning("Interrupted Exception when attempting to execute Thread.sleep()!");
×
1573
                        }
×
1574
                    } else {
1575
                        draft++;
×
1576
                        if (draft % 100 == 0) {
×
1577
                          logger.info(draft + " of  " + count + " files not yet published");
×
1578
                        }
1579
                    }
1580
                } else {
1581
                    alreadyRegistered++;
×
1582
                    if(alreadyRegistered % 100 == 0) {
×
1583
                      logger.info(alreadyRegistered + " of  " + count + " files are already registered. " + new Date());
×
1584
                    }
1585
                }
1586
            } catch (WrappedResponse ex) {
×
1587
                logger.info("Failed to register file id: " + df.getId());
×
1588
                Logger.getLogger(Datasets.class.getName()).log(Level.SEVERE, null, ex);
×
1589
            } catch (Exception e) {
×
1590
                logger.info("Unexpected Exception: " + e.getMessage());
×
1591
            }
×
1592
            
1593

1594
        }
×
1595
        logger.info("Final Results:");
×
1596
        logger.info(alreadyRegistered + " of  " + count + " files were already registered. " + new Date());
×
1597
        logger.info(draft + " of  " + count + " files are not yet published. " + new Date());
×
1598
        logger.info(released + " of  " + count + " unregistered, published files to register. " + new Date());
×
1599
        logger.info(successes + " of  " + released + " unregistered, published files registered successfully. "
×
1600
                + new Date());
1601
        logger.info(skipped + " of  " + count + " files not in collections that allow file PIDs. " + new Date());
×
1602

1603
        return ok("Datafile registration complete." + successes + " of  " + released
×
1604
                + " unregistered, published files registered successfully.");
1605
    }
1606
    
1607
    @GET
1608
    @AuthRequired
1609
    @Path("/registerDataFiles/{alias}")
1610
    public Response registerDataFilesInCollection(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("sleep") Integer sleepInterval) {
1611
        Dataverse collection;
1612
        try {
1613
            collection = findDataverseOrDie(alias);
×
1614
        } catch (WrappedResponse r) {
×
1615
            return r.getResponse();
×
1616
        }
×
1617
        
1618
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
1619
        if (superuser == null) {
×
1620
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find the superuser to execute /admin/registerDataFiles.");
×
1621
        }
1622
        
1623
        if (!systemConfig.isFilePIDsEnabledForCollection(collection)) {
×
1624
            return ok("Registration of file-level pid is disabled in collection "+alias+"; nothing to do");
×
1625
        }
1626
        
1627
        List<DataFile> dataFiles = fileService.findByDirectCollectionOwner(collection.getId());
×
1628
        Integer count = dataFiles.size();
×
1629
        Integer countSuccesses = 0;
×
1630
        Integer countAlreadyRegistered = 0;
×
1631
        Integer countReleased = 0;
×
1632
        Integer countDrafts = 0;
×
1633
        
1634
        if (sleepInterval == null) {
×
1635
            sleepInterval = 1; 
×
1636
        } else if (sleepInterval.intValue() < 1) {
×
1637
            return error(Response.Status.BAD_REQUEST, "Invalid sleep interval: "+sleepInterval);
×
1638
        }
1639
        
1640
        logger.info("Starting to register: analyzing " + count + " files. " + new Date());
×
1641
        logger.info("Only unregistered, published files will be registered.");
×
1642
        
1643
        
1644
        
1645
        for (DataFile df : dataFiles) {
×
1646
            try {
1647
                if ((df.getIdentifier() == null || df.getIdentifier().isEmpty())) {
×
1648
                    if (df.isReleased()) {
×
1649
                        countReleased++;
×
1650
                        DataverseRequest r = createDataverseRequest(superuser);
×
1651
                        execCommand(new RegisterDvObjectCommand(r, df));
×
1652
                        countSuccesses++;
×
1653
                        if (countSuccesses % 100 == 0) {
×
1654
                            logger.info(countSuccesses + " out of " + count + " files registered successfully. " + new Date());
×
1655
                        }
1656
                        try {
1657
                            Thread.sleep(sleepInterval * 1000);
×
1658
                        } catch (InterruptedException ie) {
×
1659
                            logger.warning("Interrupted Exception when attempting to execute Thread.sleep()!");
×
1660
                        }
×
1661
                    } else {
×
1662
                        countDrafts++;
×
1663
                        logger.fine(countDrafts + " out of " + count + " files not yet published");
×
1664
                    }
1665
                } else {
1666
                    countAlreadyRegistered++;
×
1667
                    logger.fine(countAlreadyRegistered + " out of " + count + " files are already registered. " + new Date());
×
1668
                }
1669
            } catch (WrappedResponse ex) {
×
1670
                countReleased++;
×
1671
                logger.info("Failed to register file id: " + df.getId());
×
1672
                Logger.getLogger(Datasets.class.getName()).log(Level.SEVERE, null, ex);
×
1673
            } catch (Exception e) {
×
1674
                logger.info("Unexpected Exception: " + e.getMessage());
×
1675
            }
×
1676
        }
×
1677
        
1678
        logger.info(countAlreadyRegistered + " out of " + count + " files were already registered. " + new Date());
×
1679
        logger.info(countDrafts + " out of " + count + " files are not yet published. " + new Date());
×
1680
        logger.info(countReleased + " out of " + count + " unregistered, published files to register. " + new Date());
×
1681
        logger.info(countSuccesses + " out of " + countReleased + " unregistered, published files registered successfully. "
×
1682
                + new Date());
1683

1684
        return ok("Datafile registration complete. " + countSuccesses + " out of " + countReleased
×
1685
                + " unregistered, published files registered successfully.");
1686
    }
1687

1688
    @GET
1689
    @AuthRequired
1690
    @Path("/updateHashValues/{alg}")
1691
    public Response updateHashValues(@Context ContainerRequestContext crc, @PathParam("alg") String alg, @QueryParam("num") int num) {
1692
        Integer count = fileService.findAll().size();
×
1693
        Integer successes = 0;
×
1694
        Integer alreadyUpdated = 0;
×
1695
        Integer rehashed = 0;
×
1696
        Integer harvested = 0;
×
1697

1698
        if (num <= 0)
×
1699
            num = Integer.MAX_VALUE;
×
1700
        DataFile.ChecksumType cType = null;
×
1701
        try {
1702
            cType = DataFile.ChecksumType.fromString(alg);
×
1703
        } catch (IllegalArgumentException iae) {
×
1704
            return error(Status.BAD_REQUEST, "Unknown algorithm");
×
1705
        }
×
1706
        logger.info("Starting to rehash: analyzing " + count + " files. " + new Date());
×
1707
        logger.info("Hashes not created with " + alg + " will be verified, and, if valid, replaced with a hash using "
×
1708
                + alg);
1709
        try {
1710
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1711
            if (!u.isSuperuser())
×
1712
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1713
        } catch (WrappedResponse e1) {
×
1714
            return error(Status.UNAUTHORIZED, "api key required");
×
1715
        }
×
1716

1717
        for (DataFile df : fileService.findAll()) {
×
1718
            if (rehashed.intValue() >= num)
×
1719
                break;
×
1720
            InputStream in = null;
×
1721
            InputStream in2 = null;
×
1722
            try {
1723
                if (df.isHarvested()) {
×
1724
                    harvested++;
×
1725
                } else {
1726
                    if (!df.getChecksumType().equals(cType)) {
×
1727

1728
                        rehashed++;
×
1729
                        logger.fine(rehashed + ": Datafile: " + df.getFileMetadata().getLabel() + ", "
×
1730
                                + df.getIdentifier());
×
1731
                        // verify hash and calc new one to replace it
1732
                        StorageIO<DataFile> storage = df.getStorageIO();
×
1733
                        storage.open(DataAccessOption.READ_ACCESS);
×
1734
                        if (!df.isTabularData()) {
×
1735
                            in = storage.getInputStream();
×
1736
                        } else {
1737
                            // if this is a tabular file, read the preserved original "auxiliary file"
1738
                            // instead:
1739
                            in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1740
                        }
1741
                        if (in == null)
×
1742
                            logger.warning("Cannot retrieve file.");
×
1743
                        String currentChecksum = FileUtil.calculateChecksum(in, df.getChecksumType());
×
1744
                        if (currentChecksum.equals(df.getChecksumValue())) {
×
1745
                            logger.fine("Current checksum for datafile: " + df.getFileMetadata().getLabel() + ", "
×
1746
                                    + df.getIdentifier() + " is valid");
×
1747
                            // Need to reset so we don't get the same stream (StorageIO class inputstreams
1748
                            // are normally only used once)
1749
                            storage.setInputStream(null);
×
1750
                            storage.open(DataAccessOption.READ_ACCESS);
×
1751
                            if (!df.isTabularData()) {
×
1752
                                in2 = storage.getInputStream();
×
1753
                            } else {
1754
                                // if this is a tabular file, read the preserved original "auxiliary file"
1755
                                // instead:
1756
                                in2 = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1757
                            }
1758
                            if (in2 == null)
×
1759
                                logger.warning("Cannot retrieve file to calculate new checksum.");
×
1760
                            String newChecksum = FileUtil.calculateChecksum(in2, cType);
×
1761

1762
                            df.setChecksumType(cType);
×
1763
                            df.setChecksumValue(newChecksum);
×
1764
                            successes++;
×
1765
                            if (successes % 100 == 0) {
×
1766
                                logger.info(
×
1767
                                        successes + " of  " + count + " files rehashed successfully. " + new Date());
1768
                            }
1769
                        } else {
×
1770
                            logger.warning("Problem: Current checksum for datafile: " + df.getFileMetadata().getLabel()
×
1771
                                    + ", " + df.getIdentifier() + " is INVALID");
×
1772
                        }
1773
                    } else {
×
1774
                        alreadyUpdated++;
×
1775
                        if (alreadyUpdated % 100 == 0) {
×
1776
                            logger.info(alreadyUpdated + " of  " + count
×
1777
                                    + " files are already have hashes with the new algorithm. " + new Date());
1778
                        }
1779
                    }
1780
                }
1781
            } catch (Exception e) {
×
1782
                logger.warning("Unexpected Exception: " + e.getMessage());
×
1783

1784
            } finally {
1785
                IOUtils.closeQuietly(in);
×
1786
                IOUtils.closeQuietly(in2);
×
1787
            }
1788
        }
×
1789
        logger.info("Final Results:");
×
1790
        logger.info(harvested + " harvested files skipped.");
×
1791
        logger.info(
×
1792
                alreadyUpdated + " of  " + count + " files already had hashes with the new algorithm. " + new Date());
1793
        logger.info(rehashed + " of  " + count + " files to rehash. " + new Date());
×
1794
        logger.info(
×
1795
                successes + " of  " + rehashed + " files successfully rehashed with the new algorithm. " + new Date());
1796

1797
        return ok("Datafile rehashing complete." + successes + " of  " + rehashed + " files successfully rehashed.");
×
1798
    }
1799
        
1800
    @POST
1801
        @AuthRequired
1802
    @Path("/computeDataFileHashValue/{fileId}/algorithm/{alg}")
1803
    public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @PathParam("alg") String alg) {
1804

1805
        try {
1806
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1807
            if (!u.isSuperuser()) {
×
1808
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1809
            }
1810
        } catch (WrappedResponse e1) {
×
1811
            return error(Status.UNAUTHORIZED, "api key required");
×
1812
        }
×
1813

1814
        DataFile fileToUpdate = null;
×
1815
        try {
1816
            fileToUpdate = findDataFileOrDie(fileId);
×
1817
        } catch (WrappedResponse r) {
×
1818
            logger.info("Could not find file with the id: " + fileId);
×
1819
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1820
        }
×
1821

1822
        if (fileToUpdate.isHarvested()) {
×
1823
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1824
        }
1825

1826
        DataFile.ChecksumType cType = null;
×
1827
        try {
1828
            cType = DataFile.ChecksumType.fromString(alg);
×
1829
        } catch (IllegalArgumentException iae) {
×
1830
            return error(Status.BAD_REQUEST, "Unknown algorithm: " + alg);
×
1831
        }
×
1832

1833
        String newChecksum = "";
×
1834

1835
        InputStream in = null;
×
1836
        try {
1837

1838
            StorageIO<DataFile> storage = fileToUpdate.getStorageIO();
×
1839
            storage.open(DataAccessOption.READ_ACCESS);
×
1840
            if (!fileToUpdate.isTabularData()) {
×
1841
                in = storage.getInputStream();
×
1842
            } else {
1843
                in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1844
            }
1845
            if (in == null) {
×
1846
                return error(Status.NOT_FOUND, "Could not retrieve file with the id: " + fileId);
×
1847
            }
1848
            newChecksum = FileUtil.calculateChecksum(in, cType);
×
1849
            fileToUpdate.setChecksumType(cType);
×
1850
            fileToUpdate.setChecksumValue(newChecksum);
×
1851

1852
        } catch (Exception e) {
×
1853
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1854

1855
        } finally {
1856
            IOUtils.closeQuietly(in);
×
1857
        }
1858

1859
        return ok("Datafile rehashing complete. " + fileId + "  successfully rehashed. New hash value is: " + newChecksum);
×
1860
    }
1861
    
1862
    @POST
1863
        @AuthRequired
1864
    @Path("/validateDataFileHashValue/{fileId}")
1865
    public Response validateDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId) {
1866

1867
        try {
1868
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1869
            if (!u.isSuperuser()) {
×
1870
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1871
            }
1872
        } catch (WrappedResponse e1) {
×
1873
            return error(Status.UNAUTHORIZED, "api key required");
×
1874
        }
×
1875

1876
        DataFile fileToValidate = null;
×
1877
        try {
1878
            fileToValidate = findDataFileOrDie(fileId);
×
1879
        } catch (WrappedResponse r) {
×
1880
            logger.info("Could not find file with the id: " + fileId);
×
1881
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1882
        }
×
1883

1884
        if (fileToValidate.isHarvested()) {
×
1885
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1886
        }
1887

1888
        DataFile.ChecksumType cType = null;
×
1889
        try {
1890
            String checkSumTypeFromDataFile = fileToValidate.getChecksumType().toString();
×
1891
            cType = DataFile.ChecksumType.fromString(checkSumTypeFromDataFile);
×
1892
        } catch (IllegalArgumentException iae) {
×
1893
            return error(Status.BAD_REQUEST, "Unknown algorithm");
×
1894
        }
×
1895

1896
        String currentChecksum = fileToValidate.getChecksumValue();
×
1897
        String calculatedChecksum = "";
×
1898
        InputStream in = null;
×
1899
        try {
1900

1901
            StorageIO<DataFile> storage = fileToValidate.getStorageIO();
×
1902
            storage.open(DataAccessOption.READ_ACCESS);
×
1903
            if (!fileToValidate.isTabularData()) {
×
1904
                in = storage.getInputStream();
×
1905
            } else {
1906
                in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1907
            }
1908
            if (in == null) {
×
1909
                return error(Status.NOT_FOUND, "Could not retrieve file with the id: " + fileId);
×
1910
            }
1911
            calculatedChecksum = FileUtil.calculateChecksum(in, cType);
×
1912

1913
        } catch (Exception e) {
×
1914
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1915
            return error(Status.BAD_REQUEST, "Checksum Validation Unexpected Exception: " + e.getMessage());
×
1916
        } finally {
1917
            IOUtils.closeQuietly(in);
×
1918

1919
        }
1920

1921
        if (currentChecksum.equals(calculatedChecksum)) {
×
1922
            return ok("Datafile validation complete for " + fileId + ". The hash value is: " + calculatedChecksum);
×
1923
        } else {
1924
            return error(Status.EXPECTATION_FAILED, "Datafile validation failed for " + fileId + ". The saved hash value is: " + currentChecksum + " while the recalculated hash value for the stored file is: " + calculatedChecksum);
×
1925
        }
1926

1927
    }
1928

1929
    @POST
1930
        @AuthRequired
1931
    @Path("/submitDatasetVersionToArchive/{id}/{version}")
1932
    public Response submitDatasetVersionToArchive(@Context ContainerRequestContext crc, @PathParam("id") String dsid,
1933
            @PathParam("version") String versionNumber) {
1934

1935
        try {
1936
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
1937

1938
            Dataset ds = findDatasetOrDie(dsid);
×
1939

1940
            DatasetVersion dv = datasetversionService.findByFriendlyVersionNumber(ds.getId(), versionNumber);
×
1941
            if(dv==null) {
×
1942
                return error(Status.BAD_REQUEST, "Requested version not found.");
×
1943
            }
1944
            if (dv.getArchivalCopyLocation() == null) {
×
1945
                String className = settingsService.getValueForKey(SettingsServiceBean.Key.ArchiverClassName);
×
1946
                // Note - the user is being sent via the createDataverseRequest(au) call to the
1947
                // back-end command where it is used to get the API Token which is
1948
                // then used to retrieve files (e.g. via S3 direct downloads) to create the Bag
1949
                AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className,
×
1950
                        createDataverseRequest(au), dv);
×
1951
                // createSubmitToArchiveCommand() tries to find and instantiate an non-abstract
1952
                // implementation of AbstractSubmitToArchiveCommand based on the provided
1953
                // className. If a class with that name isn't found (or can't be instatiated), it
1954
                // will return null
1955
                if (cmd != null) {
×
1956
                    if(ArchiverUtil.onlySingleVersionArchiving(cmd.getClass(), settingsService)) {
×
1957
                        for (DatasetVersion version : ds.getVersions()) {
×
1958
                            if ((dv != version) && version.getArchivalCopyLocation() != null) {
×
1959
                                return error(Status.CONFLICT, "Dataset already archived.");
×
1960
                            }
1961
                        } 
×
1962
                    }
1963
                    new Thread(new Runnable() {
×
1964
                        public void run() {
1965
                            try {
1966
                                DatasetVersion dv = commandEngine.submit(cmd);
×
1967
                                if (!dv.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) {
×
1968
                                    logger.info(
×
1969
                                            "DatasetVersion id=" + ds.getGlobalId().toString() + " v" + versionNumber
×
1970
                                                    + " submitted to Archive, status: " + dv.getArchivalCopyLocationStatus());
×
1971
                                } else {
1972
                                    logger.severe("Error submitting version due to conflict/error at Archive");
×
1973
                                }
1974
                            } catch (CommandException ex) {
×
1975
                                logger.log(Level.SEVERE, "Unexpected Exception calling  submit archive command", ex);
×
1976
                            }
×
1977
                        }
×
1978
                    }).start();
×
1979
                    return ok("Archive submission using " + cmd.getClass().getCanonicalName()
×
1980
                            + " started. Processing can take significant time for large datasets and requires that the user have permission to publish the dataset. View log and/or check archive for results.");
1981
                } else {
1982
                    logger.log(Level.SEVERE, "Could not find Archiver class: " + className);
×
1983
                    return error(Status.INTERNAL_SERVER_ERROR, "Could not find Archiver class: " + className);
×
1984
                }
1985
            } else {
1986
                return error(Status.BAD_REQUEST, "Version was already submitted for archiving.");
×
1987
            }
1988
        } catch (WrappedResponse e1) {
×
1989
            return e1.getResponse();
×
1990
        }
1991
    }
1992

1993
    
1994
    /**
1995
     * Iteratively archives all unarchived dataset versions
1996
     * @param
1997
     * listonly - don't archive, just list unarchived versions
1998
     * limit - max number to process
1999
     * lastestonly - only archive the latest versions
2000
     * @return
2001
     */
2002
    @POST
2003
        @AuthRequired
2004
    @Path("/archiveAllUnarchivedDatasetVersions")
2005
    public Response archiveAllUnarchivedDatasetVersions(@Context ContainerRequestContext crc, @QueryParam("listonly") boolean listonly, @QueryParam("limit") Integer limit, @QueryParam("latestonly") boolean latestonly) {
2006

2007
        try {
2008
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
2009

2010
            List<DatasetVersion> dsl = datasetversionService.getUnarchivedDatasetVersions();
×
2011
            if (dsl != null) {
×
2012
                if (listonly) {
×
2013
                    JsonArrayBuilder jab = Json.createArrayBuilder();
×
2014
                    logger.fine("Unarchived versions found: ");
×
2015
                    int current = 0;
×
2016
                    for (DatasetVersion dv : dsl) {
×
2017
                        if (limit != null && current >= limit) {
×
2018
                            break;
×
2019
                        }
2020
                        if (!latestonly || dv.equals(dv.getDataset().getLatestVersionForCopy())) {
×
2021
                            jab.add(dv.getDataset().getGlobalId().toString() + ", v" + dv.getFriendlyVersionNumber());
×
2022
                            logger.fine("    " + dv.getDataset().getGlobalId().toString() + ", v" + dv.getFriendlyVersionNumber());
×
2023
                            current++;
×
2024
                        }
2025
                    }
×
2026
                    return ok(jab); 
×
2027
                }
2028
                String className = settingsService.getValueForKey(SettingsServiceBean.Key.ArchiverClassName);
×
2029
                // Note - the user is being sent via the createDataverseRequest(au) call to the
2030
                // back-end command where it is used to get the API Token which is
2031
                // then used to retrieve files (e.g. via S3 direct downloads) to create the Bag
2032
                final DataverseRequest request = createDataverseRequest(au);
×
2033
                // createSubmitToArchiveCommand() tries to find and instantiate an non-abstract
2034
                // implementation of AbstractSubmitToArchiveCommand based on the provided
2035
                // className. If a class with that name isn't found (or can't be instatiated, it
2036
                // will return null
2037
                AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, request, dsl.get(0));
×
2038
                if (cmd != null) {
×
2039
                    //Found an archiver to use
2040
                    new Thread(new Runnable() {
×
2041
                        public void run() {
2042
                            int total = dsl.size();
×
2043
                            int successes = 0;
×
2044
                            int failures = 0;
×
2045
                            for (DatasetVersion dv : dsl) {
×
2046
                                if (limit != null && (successes + failures) >= limit) {
×
2047
                                    break;
×
2048
                                }
2049
                                if (!latestonly || dv.equals(dv.getDataset().getLatestVersionForCopy())) {
×
2050
                                    try {
2051
                                        AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, request, dv);
×
2052

2053
                                        dv = commandEngine.submit(cmd);
×
2054
                                        if (!dv.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) {
×
2055
                                            successes++;
×
2056
                                            logger.info("DatasetVersion id=" + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber() + " submitted to Archive, status: "
×
2057
                                                    + dv.getArchivalCopyLocationStatus());
×
2058
                                        } else {
2059
                                            failures++;
×
2060
                                            logger.severe("Error submitting version due to conflict/error at Archive for " + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber());
×
2061
                                        }
2062
                                    } catch (CommandException ex) {
×
2063
                                        failures++;
×
2064
                                        logger.log(Level.SEVERE, "Unexpected Exception calling  submit archive command", ex);
×
2065
                                    }
×
2066
                                }
2067
                                logger.fine(successes + failures + " of " + total + " archive submissions complete");
×
2068
                            }
×
2069
                            logger.info("Archiving complete: " + successes + " Successes, " + failures + " Failures. See prior log messages for details.");
×
2070
                        }
×
2071
                    }).start();
×
2072
                    return ok("Starting to archive all unarchived published dataset versions using " + cmd.getClass().getCanonicalName() + ". Processing can take significant time for large datasets/ large numbers of dataset versions  and requires that the user have permission to publish the dataset(s). View log and/or check archive for results.");
×
2073
                } else {
2074
                    logger.log(Level.SEVERE, "Could not find Archiver class: " + className);
×
2075
                    return error(Status.INTERNAL_SERVER_ERROR, "Could not find Archiver class: " + className);
×
2076
                }
2077
            } else {
2078
                return error(Status.BAD_REQUEST, "No unarchived published dataset versions found");
×
2079
            }
2080
        } catch (WrappedResponse e1) {
×
2081
            return e1.getResponse();
×
2082
        }
2083
    }
2084
    
2085
    @DELETE
2086
    @Path("/clearMetricsCache")
2087
    public Response clearMetricsCache() {
2088
        em.createNativeQuery("DELETE FROM metric").executeUpdate();
×
2089
        return ok("all metric caches cleared.");
×
2090
    }
2091

2092
    @DELETE
2093
    @Path("/clearMetricsCache/{name}")
2094
    public Response clearMetricsCacheByName(@PathParam("name") String name) {
2095
        Query deleteQuery = em.createNativeQuery("DELETE FROM metric where name = ?");
×
2096
        deleteQuery.setParameter(1, name);
×
2097
        deleteQuery.executeUpdate();
×
2098
        return ok("metric cache " + name + " cleared.");
×
2099
    }
2100

2101
    @GET
2102
        @AuthRequired
2103
    @Path("/dataverse/{alias}/addRoleAssignmentsToChildren")
2104
    public Response addRoleAssignementsToChildren(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2105
        Dataverse owner = dataverseSvc.findByAlias(alias);
×
2106
        if (owner == null) {
×
2107
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2108
        }
2109
        try {
2110
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2111
            if (!user.isSuperuser()) {
×
2112
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2113
            }
2114
        } catch (WrappedResponse wr) {
×
2115
            return wr.getResponse();
×
2116
        }
×
2117
        boolean inheritAllRoles = false;
×
2118
        String rolesString = settingsSvc.getValueForKey(SettingsServiceBean.Key.InheritParentRoleAssignments, "");
×
2119
        if (rolesString.length() > 0) {
×
2120
            ArrayList<String> rolesToInherit = new ArrayList<String>(Arrays.asList(rolesString.split("\\s*,\\s*")));
×
2121
            if (!rolesToInherit.isEmpty()) {
×
2122
                if (rolesToInherit.contains("*")) {
×
2123
                    inheritAllRoles = true;
×
2124
                }
2125
                return ok(dataverseSvc.addRoleAssignmentsToChildren(owner, rolesToInherit, inheritAllRoles));
×
2126
            }
2127
        }
2128
        return error(Response.Status.BAD_REQUEST,
×
2129
                "InheritParentRoleAssignments does not list any roles on this instance");
2130
    }
2131
    
2132
    @GET
2133
        @AuthRequired
2134
    @Path("/dataverse/{alias}/storageDriver")
2135
    public Response getStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2136
            Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2137
            if (dataverse == null) {
×
2138
                    return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2139
            }
2140
            try {
2141
                    AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2142
                    if (!user.isSuperuser()) {
×
2143
                            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2144
                    }
2145
            } catch (WrappedResponse wr) {
×
2146
                    return wr.getResponse();
×
2147
            }
×
2148
            //Note that this returns what's set directly on this dataverse. If null/DataAccess.UNDEFINED_STORAGE_DRIVER_IDENTIFIER, the user would have to recurse the chain of parents to find the effective storageDriver
2149
            return ok(dataverse.getStorageDriverId());
×
2150
    }
2151
    
2152
    @PUT
2153
        @AuthRequired
2154
    @Path("/dataverse/{alias}/storageDriver")
2155
    public Response setStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias, String label) throws WrappedResponse {
2156
            Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2157
            if (dataverse == null) {
×
2158
                    return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2159
            }
2160
            try {
2161
                    AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2162
                    if (!user.isSuperuser()) {
×
2163
                            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2164
                    }
2165
            } catch (WrappedResponse wr) {
×
2166
                    return wr.getResponse();
×
2167
            }
×
2168
            for (Entry<String, String> store: DataAccess.getStorageDriverLabels().entrySet()) {
×
2169
                    if(store.getKey().equals(label)) {
×
2170
                            dataverse.setStorageDriverId(store.getValue());
×
2171
                            return ok("Storage set to: " + store.getKey() + "/" + store.getValue());
×
2172
                    }
2173
            }
×
2174
            return error(Response.Status.BAD_REQUEST,
×
2175
                            "No Storage Driver found for : " + label);
2176
    }
2177

2178
    @DELETE
2179
        @AuthRequired
2180
    @Path("/dataverse/{alias}/storageDriver")
2181
    public Response resetStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2182
            Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2183
            if (dataverse == null) {
×
2184
                    return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2185
            }
2186
            try {
2187
                    AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2188
                    if (!user.isSuperuser()) {
×
2189
                            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2190
                    }
2191
            } catch (WrappedResponse wr) {
×
2192
                    return wr.getResponse();
×
2193
            }
×
2194
            dataverse.setStorageDriverId("");
×
2195
            return ok("Storage reset to default: " + DataAccess.DEFAULT_STORAGE_DRIVER_IDENTIFIER);
×
2196
    }
2197
    
2198
    @GET
2199
        @AuthRequired
2200
    @Path("/dataverse/storageDrivers")
2201
    public Response listStorageDrivers(@Context ContainerRequestContext crc) throws WrappedResponse {
2202
            try {
2203
                    AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2204
                    if (!user.isSuperuser()) {
×
2205
                            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2206
                    }
2207
            } catch (WrappedResponse wr) {
×
2208
                    return wr.getResponse();
×
2209
            }
×
2210
            JsonObjectBuilder bld = jsonObjectBuilder();
×
2211
            DataAccess.getStorageDriverLabels().entrySet().forEach(s -> bld.add(s.getKey(), s.getValue()));
×
2212
                return ok(bld);
×
2213
    }
2214
    
2215
    @GET
2216
        @AuthRequired
2217
    @Path("/dataverse/{alias}/curationLabelSet")
2218
    public Response getCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2219
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2220
        if (dataverse == null) {
×
2221
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2222
        }
2223
        try {
2224
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2225
            if (!user.isSuperuser()) {
×
2226
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2227
            }
2228
        } catch (WrappedResponse wr) {
×
2229
            return wr.getResponse();
×
2230
        }
×
2231
        // Note that this returns what's set directly on this dataverse. If
2232
        // null/SystemConfig.DEFAULTCURATIONLABELSET, the user would have to recurse the
2233
        // chain of parents to find the effective curationLabelSet
2234
        return ok(dataverse.getCurationLabelSetName());
×
2235
    }
2236

2237
    @PUT
2238
        @AuthRequired
2239
    @Path("/dataverse/{alias}/curationLabelSet")
2240
    public Response setCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("name") String name) throws WrappedResponse {
2241
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2242
        if (dataverse == null) {
×
2243
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2244
        }
2245
        try {
2246
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2247
            if (!user.isSuperuser()) {
×
2248
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2249
            }
2250
        } catch (WrappedResponse wr) {
×
2251
            return wr.getResponse();
×
2252
        }
×
2253
        if (SystemConfig.CURATIONLABELSDISABLED.equals(name) || SystemConfig.DEFAULTCURATIONLABELSET.equals(name)) {
×
2254
            dataverse.setCurationLabelSetName(name);
×
2255
            return ok("Curation Label Set Name set to: " + name);
×
2256
        } else {
2257
            for (String setName : systemConfig.getCurationLabels().keySet()) {
×
2258
                if (setName.equals(name)) {
×
2259
                    dataverse.setCurationLabelSetName(name);
×
2260
                    return ok("Curation Label Set Name set to: " + setName);
×
2261
                }
2262
            }
×
2263
        }
2264
        return error(Response.Status.BAD_REQUEST,
×
2265
                "No Curation Label Set found for : " + name);
2266
    }
2267

2268
    @DELETE
2269
        @AuthRequired
2270
    @Path("/dataverse/{alias}/curationLabelSet")
2271
    public Response resetCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2272
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2273
        if (dataverse == null) {
×
2274
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2275
        }
2276
        try {
2277
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2278
            if (!user.isSuperuser()) {
×
2279
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2280
            }
2281
        } catch (WrappedResponse wr) {
×
2282
            return wr.getResponse();
×
2283
        }
×
2284
        dataverse.setCurationLabelSetName(SystemConfig.DEFAULTCURATIONLABELSET);
×
2285
        return ok("Curation Label Set reset to default: " + SystemConfig.DEFAULTCURATIONLABELSET);
×
2286
    }
2287

2288
    @GET
2289
        @AuthRequired
2290
    @Path("/dataverse/curationLabelSets")
2291
    public Response listCurationLabelSets(@Context ContainerRequestContext crc) throws WrappedResponse {
2292
        try {
2293
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2294
            if (!user.isSuperuser()) {
×
2295
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2296
            }
2297
        } catch (WrappedResponse wr) {
×
2298
            return wr.getResponse();
×
2299
        }
×
2300
        JsonObjectBuilder bld = Json.createObjectBuilder();
×
2301

2302
        systemConfig.getCurationLabels().entrySet().forEach(s -> {
×
2303
            JsonArrayBuilder labels = Json.createArrayBuilder();
×
2304
            Arrays.asList(s.getValue()).forEach(l -> labels.add(l));
×
2305
            bld.add(s.getKey(), labels);
×
2306
        });
×
2307
        return ok(bld);
×
2308
    }
2309
    
2310
    @POST
2311
    @Path("/bannerMessage")
2312
    public Response addBannerMessage(JsonObject jsonObject) throws WrappedResponse {
2313

2314
        BannerMessage toAdd = new BannerMessage();
×
2315
        try {
2316
            String dismissible = jsonObject.getString("dismissibleByUser");
×
2317

2318
            boolean dismissibleByUser = false;
×
2319
            if (dismissible.equals("true")) {
×
2320
                dismissibleByUser = true;
×
2321
            }
2322
            toAdd.setDismissibleByUser(dismissibleByUser);
×
2323
            toAdd.setBannerMessageTexts(new ArrayList());
×
2324
            toAdd.setActive(true);
×
2325
            JsonArray jsonArray = jsonObject.getJsonArray("messageTexts");
×
2326
            for (int i = 0; i < jsonArray.size(); i++) {
×
2327
                JsonObject obj = (JsonObject) jsonArray.get(i);
×
2328
                String message = obj.getString("message");
×
2329
                String lang = obj.getString("lang");
×
2330
                BannerMessageText messageText = new BannerMessageText();
×
2331
                messageText.setMessage(message);
×
2332
                messageText.setLang(lang);
×
2333
                messageText.setBannerMessage(toAdd);
×
2334
                toAdd.getBannerMessageTexts().add(messageText);
×
2335
            }
2336
                bannerMessageService.save(toAdd);
×
2337
                return ok("Banner Message added successfully.");
×
2338

2339
        } catch (Exception e) {
×
2340
            logger.warning("Unexpected Exception: " + e.getMessage());
×
2341
            return error(Status.BAD_REQUEST, "Add Banner Message unexpected exception: " + e.getMessage());
×
2342
        }
2343

2344
    }
2345
    
2346
    @DELETE
2347
    @Path("/bannerMessage/{id}")
2348
    public Response deleteBannerMessage(@PathParam("id") Long id) throws WrappedResponse {
2349
 
2350
        BannerMessage message = em.find(BannerMessage.class, id);
×
2351
        if (message == null){
×
2352
            return error(Response.Status.NOT_FOUND, "Message id = "  + id + " not found.");
×
2353
        }
2354
        bannerMessageService.deleteBannerMessage(id);
×
2355
        
2356
        return ok("Message id =  " + id + " deleted.");
×
2357

2358
    }
2359
    
2360
    @PUT
2361
    @Path("/bannerMessage/{id}/deactivate")
2362
    public Response deactivateBannerMessage(@PathParam("id") Long id) throws WrappedResponse {
2363
        BannerMessage message = em.find(BannerMessage.class, id);
×
2364
        if (message == null){
×
2365
            return error(Response.Status.NOT_FOUND, "Message id = "  + id + " not found.");
×
2366
        }
2367
        bannerMessageService.deactivateBannerMessage(id);
×
2368
        
2369
        return ok("Message id =  " + id + " deactivated.");
×
2370

2371
    }
2372
    
2373
    @GET
2374
    @Path("/bannerMessage")
2375
    public Response getBannerMessages(@PathParam("id") Long id) throws WrappedResponse {
2376

2377
        return ok(bannerMessageService.findAllBannerMessages().stream()
×
2378
                .map(m -> jsonObjectBuilder().add("id", m.getId()).add("displayValue", m.getDisplayValue()))
×
2379
                .collect(toJsonArray()));
×
2380

2381
    }
2382
    
2383
    @POST
2384
        @AuthRequired
2385
    @Consumes("application/json")
2386
    @Path("/requestSignedUrl")
2387
    public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject urlInfo) {
2388
        AuthenticatedUser superuser = null;
×
2389
        try {
2390
            superuser = getRequestAuthenticatedUserOrDie(crc);
×
2391
        } catch (WrappedResponse wr) {
×
2392
            return wr.getResponse();
×
2393
        }
×
2394
        if (superuser == null || !superuser.isSuperuser()) {
×
2395
            return error(Response.Status.FORBIDDEN, "Requesting signed URLs is restricted to superusers.");
×
2396
        }
2397
        
2398
        String userId = urlInfo.getString("user");
×
2399
        String key=null;
×
2400
        if (userId != null) {
×
2401
            AuthenticatedUser user = authSvc.getAuthenticatedUser(userId);
×
2402
            // If a user param was sent, we sign the URL for them, otherwise on behalf of
2403
            // the superuser who made this api call
2404
            if (user != null) {
×
2405
                ApiToken apiToken = authSvc.findApiTokenByUser(user);
×
2406
                if (apiToken != null && !apiToken.isExpired() && !apiToken.isDisabled()) {
×
2407
                    key = apiToken.getTokenString();
×
2408
                }
2409
            } else {
×
2410
                userId = superuser.getUserIdentifier();
×
2411
                // We ~know this exists - the superuser just used it and it was unexpired/not
2412
                // disabled. (ToDo - if we want this to work with workflow tokens (or as a
2413
                // signed URL), we should do more checking as for the user above))
2414
                key = authSvc.findApiTokenByUser(superuser).getTokenString();
×
2415
            }
2416
            if (key == null) {
×
2417
                return error(Response.Status.CONFLICT, "Do not have a valid user with apiToken");
×
2418
            }
2419
            key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + key;
×
2420
        }
2421
        
2422
        String baseUrl = urlInfo.getString("url");
×
2423
        int timeout = urlInfo.getInt(URLTokenUtil.TIMEOUT, 10);
×
2424
        String method = urlInfo.getString(URLTokenUtil.HTTP_METHOD, "GET");
×
2425
        
2426
        String signedUrl = UrlSignerUtil.signUrl(baseUrl, timeout, userId, method, key); 
×
2427
        
2428
        return ok(Json.createObjectBuilder().add(URLTokenUtil.SIGNED_URL, signedUrl));
×
2429
    }
2430
 
2431
    @DELETE
2432
    @Path("/clearThumbnailFailureFlag")
2433
    public Response clearThumbnailFailureFlag() {
2434
        em.createNativeQuery("UPDATE dvobject SET previewimagefail = FALSE").executeUpdate();
×
2435
        return ok("Thumbnail Failure Flags cleared.");
×
2436
    }
2437
    
2438
    @DELETE
2439
    @Path("/clearThumbnailFailureFlag/{id}")
2440
    public Response clearThumbnailFailureFlagByDatafile(@PathParam("id") String fileId) {
2441
        try {
2442
            DataFile df = findDataFileOrDie(fileId);
×
2443
            Query deleteQuery = em.createNativeQuery("UPDATE dvobject SET previewimagefail = FALSE where id = ?");
×
2444
            deleteQuery.setParameter(1, df.getId());
×
2445
            deleteQuery.executeUpdate();
×
2446
            return ok("Thumbnail Failure Flag cleared for file id=: " + df.getId() + ".");
×
2447
        } catch (WrappedResponse r) {
×
2448
            logger.info("Could not find file with the id: " + fileId);
×
2449
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
2450
        }
2451
    }
2452

2453
    /**
2454
     * For testing only. Download a file from /tmp.
2455
     */
2456
    @GET
2457
    @AuthRequired
2458
    @Path("/downloadTmpFile")
2459
    public Response downloadTmpFile(@Context ContainerRequestContext crc, @QueryParam("fullyQualifiedPathToFile") String fullyQualifiedPathToFile) {
2460
        try {
2461
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2462
            if (!user.isSuperuser()) {
×
2463
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2464
            }
2465
        } catch (WrappedResponse wr) {
×
2466
            return wr.getResponse();
×
2467
        }
×
2468
        java.nio.file.Path normalizedPath = Paths.get(fullyQualifiedPathToFile).normalize();
×
2469
        if (!normalizedPath.toString().startsWith("/tmp")) {
×
2470
            return error(Status.BAD_REQUEST, "Path must begin with '/tmp' but after normalization was '" + normalizedPath +"'.");
×
2471
        }
2472
        try {
2473
            return ok(new FileInputStream(fullyQualifiedPathToFile));
×
2474
        } catch (IOException ex) {
×
2475
            return error(Status.BAD_REQUEST, ex.toString());
×
2476
        }
2477
    }
2478

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

© 2025 Coveralls, Inc