Search in sources :

Example 56 with Form

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);
}
Also used : Form(org.odk.collect.forms.Form) Instance(org.odk.collect.forms.instances.Instance) PublicKey(java.security.PublicKey) NoSuchPaddingException(javax.crypto.NoSuchPaddingException) X509EncodedKeySpec(java.security.spec.X509EncodedKeySpec) LocalizedApplicationKt.getLocalizedString(org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) EncryptionException(org.odk.collect.android.exception.EncryptionException) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) NoSuchProviderException(java.security.NoSuchProviderException) KeyFactory(java.security.KeyFactory)

Example 57 with Form

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();
}
Also used : Form(org.odk.collect.forms.Form) LocalizedApplicationKt.getLocalizedString(org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString)

Example 58 with Form

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);
    }
}
Also used : Form(org.odk.collect.forms.Form) SQLException(android.database.SQLException) ArrayList(java.util.ArrayList) LocalizedApplicationKt.getLocalizedString(org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString) File(java.io.File)

Example 59 with Form

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());
}
Also used : Form(org.odk.collect.forms.Form) DatabaseObjectMapper.getValuesFromForm(org.odk.collect.android.database.DatabaseObjectMapper.getValuesFromForm)

Example 60 with Form

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
        }
    }
}
Also used : FormSourceException(org.odk.collect.forms.FormSourceException) FormDownloaderListener(org.odk.collect.android.listeners.FormDownloaderListener) Form(org.odk.collect.forms.Form) IOException(java.io.IOException) MediaFile(org.odk.collect.forms.MediaFile) File(java.io.File)

Aggregations

Form (org.odk.collect.forms.Form)62 Test (org.junit.Test)35 File (java.io.File)22 FormsRepository (org.odk.collect.forms.FormsRepository)21 ByteArrayInputStream (java.io.ByteArrayInputStream)13 Analytics (org.odk.collect.analytics.Analytics)13 FormSource (org.odk.collect.forms.FormSource)12 FormUtils.buildForm (org.odk.collect.formstest.FormUtils.buildForm)12 MediaFile (org.odk.collect.forms.MediaFile)9 FormsRepositoryProvider (org.odk.collect.android.utilities.FormsRepositoryProvider)7 ManifestFile (org.odk.collect.forms.ManifestFile)7 Instance (org.odk.collect.forms.instances.Instance)7 LocalizedApplicationKt.getLocalizedString (org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString)7 ArrayList (java.util.ArrayList)5 FormController (org.odk.collect.android.javarosawrapper.FormController)4 ViewModelProvider (androidx.lifecycle.ViewModelProvider)3 DatabaseObjectMapper.getValuesFromForm (org.odk.collect.android.database.DatabaseObjectMapper.getValuesFromForm)3 Intent (android.content.Intent)2 View (android.view.View)2 TextView (android.widget.TextView)2