Skip to content

Commit

Permalink
Add support for importing Retroshare friends file
Browse files Browse the repository at this point in the history
  • Loading branch information
zapek committed Nov 9, 2024
1 parent 392e1dd commit 51f71f6
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,24 @@ public ResponseEntity<Void> restoreFromBackup(@RequestBody MultipartFile file) t
return ResponseEntity.ok().build();
}

@PostMapping(value = "/import-from-rs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PostMapping(value = "/import-profile-from-rs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Import a RS keyring")
@ApiResponse(responseCode = "200", description = "Request successful")
public ResponseEntity<Void> importFromRs(@RequestBody MultipartFile file, @RequestParam(value = "locationName") String locationName, @RequestParam(value = "password", required = false) String password)
public ResponseEntity<Void> importProfileFromRs(@RequestBody MultipartFile file, @RequestParam(value = "locationName") String locationName, @RequestParam(value = "password", required = false) String password)
{
backupService.importFromRs(file, locationName, password);
backupService.importProfileFromRs(file, locationName, password);
networkService.checkReadiness();

return ResponseEntity.ok().build();
}

@PostMapping(value = "/import-friends-from-rs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Import RS friends")
@ApiResponse(responseCode = "200", description = "Request successful")
public ResponseEntity<Void> importFriendsFromRs(@RequestBody MultipartFile file) throws JAXBException, IOException
{
backupService.importFriendsFromRs(file);

return ResponseEntity.ok().build();
}
}
42 changes: 38 additions & 4 deletions app/src/main/java/io/xeres/app/service/backup/BackupService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@

import io.xeres.app.crypto.pgp.PGP;
import io.xeres.app.crypto.rsa.RSA;
import io.xeres.app.crypto.rsid.RSId;
import io.xeres.app.service.IdentityService;
import io.xeres.app.service.LocationService;
import io.xeres.app.service.ProfileService;
import io.xeres.app.service.SettingsService;
import io.xeres.app.xrs.service.identity.IdentityRsService;
import io.xeres.common.id.ProfileFingerprint;
import io.xeres.common.pgp.Trust;
import io.xeres.common.rsid.Type;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.helpers.DefaultValidationEventHandler;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
Expand All @@ -50,6 +53,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

Expand All @@ -59,6 +63,8 @@ public class BackupService
private static final Logger log = LoggerFactory.getLogger(BackupService.class);

private static final long BACKUP_MAX_SIZE = 1024 * 1024 * 100L; // 100 MB
private static final long RS_PROFILE_MAX_SIZE = (long) 1024 * 1024; // 1 MB
private static final long RS_FRIENDS_MAX_SIZE = 1024 * 1024 * 10L; // 10 MB

private final ProfileService profileService;
private final LocationService locationService;
Expand Down Expand Up @@ -124,8 +130,6 @@ public void restore(MultipartFile file) throws JAXBException, IOException, Inval

var export = (Export) unmarshaller.unmarshal(file.getInputStream());

log.debug("Export is {}", export);

var localProfile = export.getProfiles().stream()
.filter(profile -> profile.getTrust() == Trust.ULTIMATE)
.findFirst().orElseThrow(() -> new IllegalArgumentException("No local profile in the profile list"));
Expand All @@ -143,14 +147,14 @@ public void restore(MultipartFile file) throws JAXBException, IOException, Inval
}

@Transactional
public void importFromRs(MultipartFile file, String locationName, String password)
public void importProfileFromRs(MultipartFile file, String locationName, String password)
{
if (file == null)
{
throw new IllegalArgumentException("RS keyring is empty");
}

if (file.getSize() >= BACKUP_MAX_SIZE)
if (file.getSize() >= RS_PROFILE_MAX_SIZE)
{
throw new IllegalArgumentException("RS keyring is too big");
}
Expand Down Expand Up @@ -208,8 +212,38 @@ public void importFromRs(MultipartFile file, String locationName, String passwor
identityRsService.generateOwnIdentity(profileName, true);
}

@Transactional
public void importFriendsFromRs(MultipartFile file) throws JAXBException, IOException
{
if (file == null)
{
throw new IllegalArgumentException("Friends file is empty");
}

if (file.getSize() >= RS_FRIENDS_MAX_SIZE)
{
throw new IllegalArgumentException("Friends file is too large");
}

JAXBContext context;
context = JAXBContext.newInstance(Root.class);

var unmarshaller = context.createUnmarshaller();
unmarshaller.setEventHandler(new DefaultValidationEventHandler()); // Display better error messages

var root = (Root) unmarshaller.unmarshal(file.getInputStream());

root.getPgpIDs().stream()
.map(PgpId::getSslIDs)
.flatMap(Collection::stream)
.map(SslId::getCertificate)
.filter(Objects::nonNull)
.forEach(certificate -> RSId.parse(certificate, Type.CERTIFICATE).ifPresent(rsId -> profileService.createOrUpdateProfile(profileService.getProfileFromRSId(rsId))));
}

private static InputStream getInputStream(MultipartFile file) throws IOException
{

if (Objects.requireNonNull(file.getOriginalFilename()).endsWith(".asc"))
{
// Skip the PGP public key block because we don't need it, and
Expand Down
45 changes: 45 additions & 0 deletions app/src/main/java/io/xeres/app/service/backup/Group.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
* Xeres is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xeres is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Xeres. If not, see <http://www.gnu.org/licenses/>.
*/

package io.xeres.app.service.backup;

import jakarta.xml.bind.annotation.XmlElement;

import java.util.List;

class Group
{
private List<PgpId> pgpIDs;

public Group()
{
// Default constructor
}

@XmlElement(name = "pgpID")
public List<PgpId> getPgpIDs()
{
return pgpIDs;
}

public void setPgpIDs(List<PgpId> pgpIDs)
{
this.pgpIDs = pgpIDs;
}
}
45 changes: 45 additions & 0 deletions app/src/main/java/io/xeres/app/service/backup/PgpId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
* Xeres is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xeres is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Xeres. If not, see <http://www.gnu.org/licenses/>.
*/

package io.xeres.app.service.backup;

import jakarta.xml.bind.annotation.XmlElement;

import java.util.List;

class PgpId
{
private List<SslId> sslIDs;

public PgpId()
{
// Default constructor
}

@XmlElement(name = "sslID")
public List<SslId> getSslIDs()
{
return sslIDs;
}

public void setSslIDs(List<SslId> sslIDs)
{
this.sslIDs = sslIDs;
}
}
62 changes: 62 additions & 0 deletions app/src/main/java/io/xeres/app/service/backup/Root.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
* Xeres is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xeres is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Xeres. If not, see <http://www.gnu.org/licenses/>.
*/

package io.xeres.app.service.backup;

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;

import java.util.List;

@XmlRootElement
class Root
{
private List<PgpId> pgpIDs;
private List<Group> groups;

public Root()
{
// Default constructor
}

@XmlElementWrapper
@XmlElement(name = "pgpID")
public List<PgpId> getPgpIDs()
{
return pgpIDs;
}

public void setPgpIDs(List<PgpId> pgpIDs)
{
this.pgpIDs = pgpIDs;
}

@XmlElementWrapper
@XmlElement(name = "group")
public List<Group> getGroups()
{
return groups;
}

public void setGroups(List<Group> groups)
{
this.groups = groups;
}
}
43 changes: 43 additions & 0 deletions app/src/main/java/io/xeres/app/service/backup/SslId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
* Xeres is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xeres is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Xeres. If not, see <http://www.gnu.org/licenses/>.
*/

package io.xeres.app.service.backup;

import jakarta.xml.bind.annotation.XmlAttribute;

class SslId
{
private String certificate;

public SslId()
{
// Default constructor
}

@XmlAttribute(name = "certificate")
public String getCertificate()
{
return certificate;
}

public void setCertificate(String certificate)
{
this.certificate = certificate;
}
}
12 changes: 11 additions & 1 deletion ui/src/main/java/io/xeres/ui/client/ConfigClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public Mono<Void> sendRsKeyring(File file, String locationName, String password)
{
return webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/import-from-rs")
.path("/import-profile-from-rs")
.queryParam("locationName", locationName)
.queryParam("password", password)
.build())
Expand All @@ -175,4 +175,14 @@ public Mono<Void> sendRsKeyring(File file, String locationName, String password)
.retrieve()
.bodyToMono(Void.class);
}

public Mono<Void> sendRsFriends(File file)
{
return webClient.post()
.uri("/import-friends-from-rs")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(fromFile(file)))
.retrieve()
.bodyToMono(Void.class);
}
}
18 changes: 18 additions & 0 deletions ui/src/main/java/io/xeres/ui/controller/MainWindowController.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ public class MainWindowController implements WindowController
@FXML
private MenuItem exportBackup;

@FXML
private MenuItem importFriends;

@FXML
private MenuItem statistics;

Expand Down Expand Up @@ -274,6 +277,21 @@ public void initialize()
}
});

importFriends.setOnAction(event -> {
var fileChooser = new FileChooser();
fileChooser.setTitle(bundle.getString("main.import-friends"));
fileChooser.setInitialDirectory(new File(AppDirsFactory.getInstance().getUserDownloadsDir(null, null, null)));
fileChooser.getExtensionFilters().add(new ExtensionFilter(bundle.getString("file-requester.xml"), "*.xml"));
var selectedFile = fileChooser.showOpenDialog(getWindow(event));
if (selectedFile != null && selectedFile.canRead())
{
configClient.sendRsFriends(selectedFile)
.doOnSuccess(unused -> Platform.runLater(() -> UiUtils.alert(Alert.AlertType.INFORMATION, "Friends imported successfully.")))
.doOnError(UiUtils::showAlertError)
.subscribe();
}
});

statistics.setOnAction(event -> windowManager.openStatistics());

if (environment.acceptsProfiles(Profiles.of("dev")))
Expand Down
1 change: 1 addition & 0 deletions ui/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ main.home.qrcode.tip=Use the QR code to transfer your ID. Print it or take a pic

main.select-avatar=Select Avatar Picture
main.export-profile=Select where to save your profile
main.import-friends=Select the Retroshare friends file

main.scanning=Scanning {0}...
main.hashing=Hashing {0}
Expand Down
Loading

0 comments on commit 51f71f6

Please sign in to comment.