use of org.odk.collect.forms.Form in project collect by opendatakit.
the class EncryptionUtils method getEncryptedFormInformation.
/**
* Retrieve the encryption information for this uri.
*
* @param uri an Instance uri
* @param instanceMetadata the metadata for this instance used to check if the form definition
* defines an instanceID
* @return an {@link EncryptedFormInformation} object if the form definition requests encryption
* and the record can be encrypted. {@code null} if the form definition does not request
* encryption or if the BouncyCastle implementation is not present.
* @throws EncryptionException if the form definition requests encryption but the record can't
* be encrypted
*/
public static EncryptedFormInformation getEncryptedFormInformation(Uri uri, InstanceMetadata instanceMetadata) throws EncryptionException {
// fetch the form information
String formId;
String formVersion;
PublicKey pk;
Form form = null;
if (InstancesContract.CONTENT_ITEM_TYPE.equals(Collect.getInstance().getContentResolver().getType(uri))) {
Instance instance = new InstancesRepositoryProvider(Collect.getInstance()).get().get(ContentUriHelper.getIdFromUri(uri));
if (instance == null) {
String msg = getLocalizedString(Collect.getInstance(), R.string.not_exactly_one_record_for_this_instance);
Timber.e(msg);
throw new EncryptionException(msg, null);
}
formId = instance.getFormId();
formVersion = instance.getFormVersion();
List<Form> forms = new FormsRepositoryProvider(Collect.getInstance()).get().getAllByFormIdAndVersion(formId, formVersion);
// forms with the same formid/version as long as only one is active (not deleted).
if (forms.isEmpty() || new FormsRepositoryProvider(Collect.getInstance()).get().getAllNotDeletedByFormIdAndVersion(formId, formVersion).size() > 1) {
String msg = getLocalizedString(Collect.getInstance(), R.string.not_exactly_one_blank_form_for_this_form_id);
Timber.d(msg);
throw new EncryptionException(msg, null);
}
form = forms.get(0);
} else if (FormsContract.CONTENT_ITEM_TYPE.equals(Collect.getInstance().getContentResolver().getType(uri))) {
throw new IllegalArgumentException("Can't get encryption info for Form URI!");
}
formId = form.getFormId();
if (formId == null || formId.length() == 0) {
String msg = getLocalizedString(Collect.getInstance(), R.string.no_form_id_specified);
Timber.d(msg);
throw new EncryptionException(msg, null);
}
formVersion = form.getVersion();
String base64RsaPublicKey = form.getBASE64RSAPublicKey();
if (base64RsaPublicKey == null) {
// this is legitimately not an encrypted form
return null;
}
byte[] publicKey = Base64.decode(base64RsaPublicKey, Base64.NO_WRAP);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf;
try {
kf = KeyFactory.getInstance(RSA_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
String msg = getLocalizedString(Collect.getInstance(), R.string.phone_does_not_support_rsa);
Timber.d(e, "%s due to %s ", msg, e.getMessage());
throw new EncryptionException(msg, e);
}
try {
pk = kf.generatePublic(publicKeySpec);
} catch (InvalidKeySpecException e) {
String msg = getLocalizedString(Collect.getInstance(), R.string.invalid_rsa_public_key);
Timber.d(e, "%s due to %s ", msg, e.getMessage());
throw new EncryptionException(msg, e);
}
// submission must have an OpenRosa metadata block with a non-null instanceID
if (instanceMetadata.instanceId == null) {
throw new EncryptionException("This form does not specify an instanceID. You must specify one to enable encryption.", null);
}
// https://code.google.com/p/opendatakit/issues/detail?id=918
try {
Cipher.getInstance(EncryptionUtils.SYMMETRIC_ALGORITHM, ENCRYPTION_PROVIDER);
} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
String msg;
if (e instanceof NoSuchAlgorithmException) {
msg = "No BouncyCastle implementation of symmetric algorithm!";
} else if (e instanceof NoSuchProviderException) {
msg = "No BouncyCastle provider implementation of symmetric algorithm!";
} else {
msg = "No BouncyCastle provider for padding implementation of symmetric algorithm!";
}
Timber.d(msg);
return null;
}
return new EncryptedFormInformation(formId, formVersion, instanceMetadata, pk);
}
use of org.odk.collect.forms.Form in project collect by opendatakit.
the class FormsDirDiskFormsSynchronizer method parseForm.
private Form parseForm(File formDefFile) throws IllegalArgumentException {
// Probably someone overwrite the file on the sdcard
// So re-parse it and update it's information
Form.Builder builder = new Form.Builder();
HashMap<String, String> fields;
try {
fields = FileUtils.getMetadataFromFormDefinition(formDefFile);
} catch (RuntimeException e) {
throw new IllegalArgumentException(formDefFile.getName() + " :: " + e.toString());
}
// update date
Long now = System.currentTimeMillis();
builder.date(now);
String title = fields.get(FileUtils.TITLE);
if (title != null) {
builder.displayName(title);
} else {
throw new IllegalArgumentException(getLocalizedString(Collect.getInstance(), R.string.xform_parse_error, formDefFile.getName(), "title"));
}
String formid = fields.get(FileUtils.FORMID);
if (formid != null) {
builder.formId(formid);
} else {
throw new IllegalArgumentException(getLocalizedString(Collect.getInstance(), R.string.xform_parse_error, formDefFile.getName(), "id"));
}
String version = fields.get(FileUtils.VERSION);
if (version != null) {
builder.version(version);
}
String submission = fields.get(FileUtils.SUBMISSIONURI);
if (submission != null) {
if (Validator.isUrlValid(submission)) {
builder.submissionUri(submission);
} else {
throw new IllegalArgumentException(getLocalizedString(Collect.getInstance(), R.string.xform_parse_error, formDefFile.getName(), "submission url"));
}
}
String base64RsaPublicKey = fields.get(FileUtils.BASE64_RSA_PUBLIC_KEY);
if (base64RsaPublicKey != null) {
builder.base64RSAPublicKey(base64RsaPublicKey);
}
builder.autoDelete(fields.get(FileUtils.AUTO_DELETE));
builder.autoSend(fields.get(FileUtils.AUTO_SEND));
builder.geometryXpath(fields.get(FileUtils.GEOMETRY_XPATH));
// Note, the path doesn't change here, but it needs to be included so the
// update will automatically update the .md5 and the cache path.
builder.formFilePath(formDefFile.getAbsolutePath());
builder.formMediaPath(FileUtils.constructMediaPath(formDefFile.getAbsolutePath()));
return builder.build();
}
use of org.odk.collect.forms.Form in project collect by opendatakit.
the class FormsDirDiskFormsSynchronizer method synchronizeAndReturnError.
public String synchronizeAndReturnError() {
String statusMessage = "";
int instance = ++counter;
Timber.i("[%d] doInBackground begins!", instance);
List<Long> idsToDelete = new ArrayList<>();
try {
// Process everything then report what didn't work.
StringBuilder errors = new StringBuilder();
File formDir = new File(formsDir);
if (formDir.exists() && formDir.isDirectory()) {
// Get all the files in the /odk/foms directory
File[] formDefs = formDir.listFiles();
// Step 1: assemble the candidate form files
List<File> formsToAdd = filterFormsToAdd(formDefs, instance);
// Step 2: quickly run through and figure out what files we need to
// parse and update; this is quick, as we only calculate the md5
// and see if it has changed.
List<IdFile> uriToUpdate = new ArrayList<>();
List<Form> forms = formsRepository.getAll();
for (Form form : forms) {
// For each element in the provider, see if the file already exists
String sqlFilename = form.getFormFilePath();
String md5 = form.getMD5Hash();
File sqlFile = new File(sqlFilename);
if (sqlFile.exists()) {
// remove it from the list of forms (we only want forms
// we haven't added at the end)
formsToAdd.remove(sqlFile);
String md5Computed = Md5.getMd5Hash(sqlFile);
if (md5Computed == null || md5 == null || !md5Computed.equals(md5)) {
// Probably someone overwrite the file on the sdcard
// So re-parse it and update it's information
Long id = form.getDbId();
uriToUpdate.add(new IdFile(id, sqlFile));
}
} else {
// File not found in sdcard but file path found in database
// probably because the file has been deleted or filename was changed in sdcard
// Add the ID to list so that they could be deleted all together
Long id = form.getDbId();
idsToDelete.add(id);
}
}
// Delete the forms not found in sdcard from the database
for (Long id : idsToDelete) {
formsRepository.delete(id);
}
// Step3: go through uriToUpdate to parse and update each in turn.
// Note: buildContentValues calls getMetadataFromFormDefinition which parses the
// form XML. This takes time for large forms and/or slow devices.
// Big win if multiple DiskSyncTasks running
Collections.shuffle(uriToUpdate);
for (IdFile entry : uriToUpdate) {
File formDefFile = entry.file;
// Probably someone overwrite the file on the sdcard
// So re-parse it and update it's information
Form form;
try {
form = parseForm(formDefFile);
} catch (IllegalArgumentException e) {
errors.append(e.getMessage()).append("\r\n");
File badFile = new File(formDefFile.getParentFile(), formDefFile.getName() + ".bad");
badFile.delete();
formDefFile.renameTo(badFile);
continue;
}
formsRepository.save(new Form.Builder(form).dbId(entry.id).build());
}
uriToUpdate.clear();
// Step 4: go through the newly-discovered files in xFormsToAdd and add them.
// This is slow because buildContentValues(...) is slow.
//
// Big win if multiple DiskSyncTasks running
Collections.shuffle(formsToAdd);
while (!formsToAdd.isEmpty()) {
File formDefFile = formsToAdd.remove(0);
// Skip this file if that is the case.
if (formsRepository.getOneByPath(formDefFile.getAbsolutePath()) != null) {
Timber.i("[%d] skipping -- definition already recorded: %s", instance, formDefFile.getAbsolutePath());
continue;
}
// Parse it for the first time...
Form form;
try {
form = parseForm(formDefFile);
} catch (IllegalArgumentException e) {
errors.append(e.getMessage()).append("\r\n");
File badFile = new File(formDefFile.getParentFile(), formDefFile.getName() + ".bad");
badFile.delete();
formDefFile.renameTo(badFile);
continue;
}
// insert into content provider
try {
// insert failures are OK and expected if multiple
// DiskSync scanners are active.
formsRepository.save(form);
Analytics.log(AnalyticsEvents.IMPORT_FORM);
} catch (SQLException e) {
Timber.i("[%d] %s", instance, e.toString());
}
}
}
if (errors.length() != 0) {
statusMessage = errors.toString();
} else {
Timber.d(getLocalizedString(Collect.getInstance(), R.string.finished_disk_scan));
}
return statusMessage;
} finally {
Timber.i("[%d] doInBackground ends!", instance);
}
}
use of org.odk.collect.forms.Form in project collect by opendatakit.
the class FormsProvider method insert.
@Override
public synchronized Uri insert(@NonNull Uri uri, ContentValues initialValues) {
deferDaggerInit();
// Validate the requested uri
if (URI_MATCHER.match(uri) != FORMS) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
String projectId = getProjectId(uri);
logServerEvent(projectId, AnalyticsEvents.FORMS_PROVIDER_INSERT);
String formsPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS, projectId);
String cachePath = storagePathProvider.getOdkDirPath(StorageSubdirectory.CACHE, projectId);
Form form = getFormsRepository(projectId).save(getFormFromValues(initialValues, formsPath, cachePath));
return FormsContract.getUri(projectId, form.getDbId());
}
use of org.odk.collect.forms.Form in project collect by opendatakit.
the class ServerFormDownloader method downloadForm.
@Override
public void downloadForm(ServerFormDetails form, @Nullable ProgressReporter progressReporter, @Nullable Supplier<Boolean> isCancelled) throws FormDownloadException {
Form formOnDevice;
try {
formOnDevice = formsRepository.getOneByMd5Hash(validateHash(form.getHash()));
} catch (IllegalArgumentException e) {
throw new FormDownloadException.FormWithNoHash();
}
if (formOnDevice != null) {
if (formOnDevice.isDeleted()) {
formsRepository.restore(formOnDevice.getDbId());
}
} else {
List<Form> allSameFormIdVersion = formsRepository.getAllByFormIdAndVersion(form.getFormId(), form.getFormVersion());
if (!allSameFormIdVersion.isEmpty() && !form.getDownloadUrl().contains("/draft.xml")) {
analytics.logEventWithParam(DOWNLOAD_SAME_FORMID_VERSION_DIFFERENT_HASH, "form", AnalyticsUtils.getFormHash(form.getFormId(), form.getFormName()));
}
}
File tempDir = new File(cacheDir, "download-" + UUID.randomUUID().toString());
tempDir.mkdirs();
try {
FormDownloaderListener stateListener = new ProgressReporterAndSupplierStateListener(progressReporter, isCancelled);
processOneForm(form, stateListener, tempDir, formsDirPath, formMetadataParser);
} catch (FormSourceException e) {
throw new FormDownloadException.FormSourceError(e);
} finally {
try {
deleteDirectory(tempDir);
} catch (IOException ignored) {
// ignored
}
}
}
Aggregations