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

IQSS / dataverse / #21824

20 Mar 2024 08:05PM CUT coverage: 20.661% (+0.09%) from 20.57%
#21824

push

github

web-flow
Merge pull request #10211 from IQSS/9356-rate-limiting-command-engine

adding rate limiting for command engine

90 of 123 new or added lines in 14 files covered. (73.17%)

1 existing line in 1 file now uncovered.

17074 of 82639 relevant lines covered (20.66%)

0.21 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.DvObjectServiceBean;
18
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
19
import edu.harvard.iq.dataverse.settings.JvmSettings;
20
import edu.harvard.iq.dataverse.validation.EMailValidator;
21
import edu.harvard.iq.dataverse.EjbDataverseEngine;
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.pidproviders.handle.HandlePidProvider;
101
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
102
import edu.harvard.iq.dataverse.userdata.UserListMaker;
103
import edu.harvard.iq.dataverse.userdata.UserListResult;
104
import edu.harvard.iq.dataverse.util.ArchiverUtil;
105
import edu.harvard.iq.dataverse.util.BundleUtil;
106
import edu.harvard.iq.dataverse.util.FileUtil;
107
import edu.harvard.iq.dataverse.util.SystemConfig;
108
import edu.harvard.iq.dataverse.util.URLTokenUtil;
109
import edu.harvard.iq.dataverse.util.UrlSignerUtil;
110

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

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

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

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

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

179
    // Make the session available
180
    @Inject
181
    DataverseSession session;
182

183
        public static final String listUsersPartialAPIPath = "list-users";
184
        public static final String listUsersFullAPIPath = "/api/admin/" + listUsersPartialAPIPath;
185

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

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

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

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

213
                return (s != null) ? ok(s) : notFound("Setting " + name + " not found");
×
214
        }
215

216
        @Path("settings/{name}")
217
        @DELETE
218
        public Response deleteSetting(@PathParam("name") String name) {
219
                settingsSvc.delete(name);
×
220

221
                return ok("Setting " + name + " deleted.");
×
222
        }
223

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

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

245
        Dataverse dv = doomed.getDataverse();
×
246
        List <Dataverse> dataverseWDefaultTemplate = templateService.findDataversesByDefaultTemplateId(doomed.getId());
×
247

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

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

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

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

295
            return ok(container);
×
296

297
        
298
    }
299

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

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

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

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

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

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

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

366
                row.setEnabled(enable);
×
367
                em.merge(row);
×
368

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

557
                User authUser = getRequestUser(crc);
×
558

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

564
                UserListMaker userListMaker = new UserListMaker(userService);
×
565

566
                // String sortKey = null;
567
                UserListResult userListResult = userListMaker.runUserSearch(searchTerm, itemsPerPage, selectedPage, sortKey);
×
568

569
                return ok(userListResult.toJSON());
×
570
        }
571

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

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

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

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

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

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

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

991

992

993

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

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

1020
    @DELETE
1021
        @AuthRequired
1022
    @Path("roles/{id}")
1023
    public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1024

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

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

1043
                        user.setSuperuser(!user.isSuperuser());
×
1044

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

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

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

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

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

1160
        Long dbId = dataset.getId();
×
1161

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

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

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

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

1263
        @Path("assignments/assignees/{raIdtf: .*}")
1264
        @GET
1265
        public Response getAssignmentsFor(@PathParam("raIdtf") String raIdtf) {
1266

1267
                JsonArrayBuilder arr = Json.createArrayBuilder();
×
1268
                roleAssigneeSvc.getAssignmentsFor(raIdtf).forEach(a -> arr.add(json(a)));
×
1269

1270
                return ok(arr);
×
1271
        }
1272

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

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

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

1333
        }
1334

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

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

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

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

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

1376
                List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalTypes();
×
1377

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

1389
                ingestService.fixMissingOriginalTypes(affectedFileIds);
×
1390

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

1399
        List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalSizes();
×
1400

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

1418
        ingestService.fixMissingOriginalSizes(affectedFileIds);
×
1419
        return ok(info);
×
1420
    }
1421

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

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

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

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

1474
    @POST
1475
        @AuthRequired
1476
    @Path("{id}/reregisterHDLToPID")
1477
    public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1478
        logger.info("Starting to reregister  " + id + " Dataset Id. (from hdl to doi)" + new Date());
×
1479
        try {
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
            
1491
            if (HandlePidProvider.HDL_PROTOCOL.equals(dvObjectService.getEffectivePidGenerator(ds).getProtocol())) {
×
1492
                logger.info("Bad Request protocol set to handle  " );
×
1493
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.set.for.doi"));
×
1494
            }
1495
            if (ds.getIdentifier() != null && !ds.getIdentifier().isEmpty() && ds.getProtocol().equals(HandlePidProvider.HDL_PROTOCOL)) {
×
1496
                execCommand(new RegisterDvObjectCommand(r, ds, true));
×
1497
            } else {
1498
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.hdl.dataset"));
×
1499
            }
1500

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

1513
    @GET
1514
    @AuthRequired
1515
    @Path("{id}/registerDataFile")
1516
    public Response registerDataFile(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1517
        logger.info("Starting to register  " + id + " file id. " + new Date());
×
1518

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

1532
        } catch (WrappedResponse r) {
×
1533
            logger.info("Failed to register file id: " + id);
×
1534
        } catch (Exception e) {
×
1535
            logger.info("Failed to register file id: " + id + " Unexpecgted Exception " + e.getMessage());
×
1536
        }
×
1537
        return ok("Datafile registration complete. File registered successfully.");
×
1538
    }
1539

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

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

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

1689
        return ok("Datafile registration complete. " + countSuccesses + " out of " + countReleased
×
1690
                + " unregistered, published files registered successfully.");
1691
    }
1692

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

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

1722
        for (DataFile df : fileService.findAll()) {
×
1723
            if (rehashed.intValue() >= num)
×
1724
                break;
×
1725
            InputStream in = null;
×
1726
            InputStream in2 = null;
×
1727
            try {
1728
                if (df.isHarvested()) {
×
1729
                    harvested++;
×
1730
                } else {
1731
                    if (!df.getChecksumType().equals(cType)) {
×
1732

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

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

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

1802
        return ok("Datafile rehashing complete." + successes + " of  " + rehashed + " files successfully rehashed.");
×
1803
    }
1804
        
1805
    @POST
1806
        @AuthRequired
1807
    @Path("/computeDataFileHashValue/{fileId}/algorithm/{alg}")
1808
    public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @PathParam("alg") String alg) {
1809

1810
        try {
1811
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1812
            if (!u.isSuperuser()) {
×
1813
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1814
            }
1815
        } catch (WrappedResponse e1) {
×
1816
            return error(Status.UNAUTHORIZED, "api key required");
×
1817
        }
×
1818

1819
        DataFile fileToUpdate = null;
×
1820
        try {
1821
            fileToUpdate = findDataFileOrDie(fileId);
×
1822
        } catch (WrappedResponse r) {
×
1823
            logger.info("Could not find file with the id: " + fileId);
×
1824
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1825
        }
×
1826

1827
        if (fileToUpdate.isHarvested()) {
×
1828
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1829
        }
1830

1831
        DataFile.ChecksumType cType = null;
×
1832
        try {
1833
            cType = DataFile.ChecksumType.fromString(alg);
×
1834
        } catch (IllegalArgumentException iae) {
×
1835
            return error(Status.BAD_REQUEST, "Unknown algorithm: " + alg);
×
1836
        }
×
1837

1838
        String newChecksum = "";
×
1839

1840
        InputStream in = null;
×
1841
        try {
1842

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

1857
        } catch (Exception e) {
×
1858
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1859

1860
        } finally {
1861
            IOUtils.closeQuietly(in);
×
1862
        }
1863

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

1872
        try {
1873
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1874
            if (!u.isSuperuser()) {
×
1875
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1876
            }
1877
        } catch (WrappedResponse e1) {
×
1878
            return error(Status.UNAUTHORIZED, "api key required");
×
1879
        }
×
1880

1881
        DataFile fileToValidate = null;
×
1882
        try {
1883
            fileToValidate = findDataFileOrDie(fileId);
×
1884
        } catch (WrappedResponse r) {
×
1885
            logger.info("Could not find file with the id: " + fileId);
×
1886
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1887
        }
×
1888

1889
        if (fileToValidate.isHarvested()) {
×
1890
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1891
        }
1892

1893
        DataFile.ChecksumType cType = null;
×
1894
        try {
1895
            String checkSumTypeFromDataFile = fileToValidate.getChecksumType().toString();
×
1896
            cType = DataFile.ChecksumType.fromString(checkSumTypeFromDataFile);
×
1897
        } catch (IllegalArgumentException iae) {
×
1898
            return error(Status.BAD_REQUEST, "Unknown algorithm");
×
1899
        }
×
1900

1901
        String currentChecksum = fileToValidate.getChecksumValue();
×
1902
        String calculatedChecksum = "";
×
1903
        InputStream in = null;
×
1904
        try {
1905

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

1918
        } catch (Exception e) {
×
1919
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1920
            return error(Status.BAD_REQUEST, "Checksum Validation Unexpected Exception: " + e.getMessage());
×
1921
        } finally {
1922
            IOUtils.closeQuietly(in);
×
1923

1924
        }
1925

1926
        if (currentChecksum.equals(calculatedChecksum)) {
×
1927
            return ok("Datafile validation complete for " + fileId + ". The hash value is: " + calculatedChecksum);
×
1928
        } else {
1929
            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);
×
1930
        }
1931

1932
    }
1933

1934
    @POST
1935
        @AuthRequired
1936
    @Path("/submitDatasetVersionToArchive/{id}/{version}")
1937
    public Response submitDatasetVersionToArchive(@Context ContainerRequestContext crc, @PathParam("id") String dsid,
1938
            @PathParam("version") String versionNumber) {
1939

1940
        try {
1941
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
1942

1943
            Dataset ds = findDatasetOrDie(dsid);
×
1944

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

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

2012
        try {
2013
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
2014

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

2058
                                        dv = commandEngine.submit(cmd);
×
2059
                                        if (!dv.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) {
×
2060
                                            successes++;
×
2061
                                            logger.info("DatasetVersion id=" + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber() + " submitted to Archive, status: "
×
2062
                                                    + dv.getArchivalCopyLocationStatus());
×
2063
                                        } else {
2064
                                            failures++;
×
2065
                                            logger.severe("Error submitting version due to conflict/error at Archive for " + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber());
×
2066
                                        }
2067
                                    } catch (CommandException ex) {
×
2068
                                        failures++;
×
2069
                                        logger.log(Level.SEVERE, "Unexpected Exception calling  submit archive command", ex);
×
2070
                                    }
×
2071
                                }
2072
                                logger.fine(successes + failures + " of " + total + " archive submissions complete");
×
2073
                            }
×
2074
                            logger.info("Archiving complete: " + successes + " Successes, " + failures + " Failures. See prior log messages for details.");
×
2075
                        }
×
2076
                    }).start();
×
2077
                    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.");
×
2078
                } else {
2079
                    logger.log(Level.SEVERE, "Could not find Archiver class: " + className);
×
2080
                    return error(Status.INTERNAL_SERVER_ERROR, "Could not find Archiver class: " + className);
×
2081
                }
2082
            } else {
2083
                return error(Status.BAD_REQUEST, "No unarchived published dataset versions found");
×
2084
            }
2085
        } catch (WrappedResponse e1) {
×
2086
            return e1.getResponse();
×
2087
        }
2088
    }
2089
    
2090
    @DELETE
2091
    @Path("/clearMetricsCache")
2092
    public Response clearMetricsCache() {
2093
        em.createNativeQuery("DELETE FROM metric").executeUpdate();
×
2094
        return ok("all metric caches cleared.");
×
2095
    }
2096

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

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

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

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

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

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

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

2319
        BannerMessage toAdd = new BannerMessage();
×
2320
        try {
2321
            String dismissible = jsonObject.getString("dismissibleByUser");
×
2322

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

2344
        } catch (Exception e) {
×
2345
            logger.warning("Unexpected Exception: " + e.getMessage());
×
2346
            return error(Status.BAD_REQUEST, "Add Banner Message unexpected exception: " + e.getMessage());
×
2347
        }
2348

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

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

2376
    }
2377
    
2378
    @GET
2379
    @Path("/bannerMessage")
2380
    public Response getBannerMessages(@PathParam("id") Long id) throws WrappedResponse {
2381

2382
        return ok(bannerMessageService.findAllBannerMessages().stream()
×
2383
                .map(m -> jsonObjectBuilder().add("id", m.getId()).add("displayValue", m.getDisplayValue()))
×
2384
                .collect(toJsonArray()));
×
2385

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

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

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