mirror of
https://github.com/sismics/docs.git
synced 2024-11-16 11:17:57 +01:00
commit
3a32b742e8
@ -26,9 +26,10 @@ Features
|
||||
- Full text search in image and PDF
|
||||
- SHA-256 encryption
|
||||
- Tag system
|
||||
- Multi-users
|
||||
- Document sharing
|
||||
- Multi-users ACL system
|
||||
- Document sharing by URL
|
||||
- RESTful Web API
|
||||
- Modern Android client
|
||||
|
||||
Download
|
||||
--------
|
||||
|
@ -75,6 +75,12 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.1.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.0.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.1.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
||||
@ -100,16 +106,16 @@
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
|
||||
<orderEntry type="library" exported="" name="eventbus-2.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
|
||||
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-22.1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-22.1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="recyclerview-v7-22.0.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-22.1.0" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -3,7 +3,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.1.0'
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
}
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
@ -28,6 +28,21 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file(System.getenv("TRACKINO_STORE_PATH"))
|
||||
storePassword System.getenv("TRACKINO_STORE_PASS")
|
||||
keyAlias System.getenv("TRACKINO_STORE_ALIAS")
|
||||
keyPassword System.getenv("TRACKINO_STORE_KEYPASS")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
@ -35,7 +50,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'com.android.support:appcompat-v7:22.1.0'
|
||||
compile 'com.android.support:appcompat-v7:22.1.1'
|
||||
compile 'com.android.support:recyclerview-v7:22.0.0'
|
||||
compile 'com.loopj.android:android-async-http:1.4.6'
|
||||
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||
|
@ -16,7 +16,8 @@
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name=".activity.LoginActivity"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppThemeDark">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ -25,7 +26,8 @@
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustNothing">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
|
@ -84,8 +84,10 @@ public class DocumentEditActivity extends AppCompatActivity {
|
||||
|
||||
// Setup the activity
|
||||
setContentView(R.layout.document_edit_activity);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
languageSpinner = (Spinner) findViewById(R.id.languageSpinner);
|
||||
tagsEditText = (TagsCompleteTextView) findViewById(R.id.tagsEditText);
|
||||
datePickerView = (DatePickerView) findViewById(R.id.dateEditText);
|
||||
@ -93,7 +95,7 @@ public class DocumentEditActivity extends AppCompatActivity {
|
||||
descriptionEditText = (EditText) findViewById(R.id.descriptionEditText);
|
||||
|
||||
// Language spinner
|
||||
LanguageAdapter languageAdapter = new LanguageAdapter(this);
|
||||
LanguageAdapter languageAdapter = new LanguageAdapter(this, false);
|
||||
languageSpinner.setAdapter(languageAdapter);
|
||||
|
||||
// Tags auto-complete
|
||||
|
@ -9,19 +9,21 @@ import android.provider.SearchRecentSuggestions;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.androidquery.util.AQUtility;
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.adapter.TagListAdapter;
|
||||
import com.sismics.docs.event.AdvancedSearchEvent;
|
||||
import com.sismics.docs.event.SearchEvent;
|
||||
import com.sismics.docs.fragment.SearchFragment;
|
||||
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.provider.RecentSuggestionsProvider;
|
||||
@ -137,6 +139,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -154,6 +158,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
return true;
|
||||
|
||||
case R.id.advanced_search:
|
||||
SearchFragment dialog = SearchFragment.newInstance();
|
||||
dialog.show(getSupportFragmentManager(), "SearchFragment");
|
||||
return true;
|
||||
|
||||
case R.id.settings:
|
||||
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
|
||||
return true;
|
||||
@ -253,8 +262,18 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerLayout.closeDrawers();
|
||||
}
|
||||
|
||||
/**
|
||||
* An advanced search event has been fired.
|
||||
*
|
||||
* @param event Advanced search event
|
||||
*/
|
||||
public void onEventMainThread(AdvancedSearchEvent event) {
|
||||
searchQuery(event.getQuery());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
EventBus.getDefault().unregister(this);
|
||||
if(isTaskRoot()) {
|
||||
int cacheSizeMb = PreferenceUtil.getIntegerPreference(this, PreferenceUtil.PREF_CACHE_SIZE, 10);
|
||||
AQUtility.cleanCacheAsync(this, cacheSizeMb * 1000000, cacheSizeMb * 1000000);
|
||||
|
@ -25,9 +25,12 @@ public class LanguageAdapter extends BaseAdapter {
|
||||
|
||||
private List<Language> languageList;
|
||||
|
||||
public LanguageAdapter(Context context) {
|
||||
public LanguageAdapter(Context context, boolean noValue) {
|
||||
this.context = context;
|
||||
this.languageList = new ArrayList<>();
|
||||
if (noValue) {
|
||||
languageList.add(new Language("", R.string.all_languages, 0));
|
||||
}
|
||||
languageList.add(new Language("fra", R.string.language_french, R.drawable.fra));
|
||||
languageList.add(new Language("eng", R.string.language_english, R.drawable.eng));
|
||||
languageList.add(new Language("jpn", R.string.language_japanese, R.drawable.jpn));
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.sismics.docs.event;
|
||||
|
||||
/**
|
||||
* Advanced search event.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class AdvancedSearchEvent {
|
||||
/**
|
||||
* Search query.
|
||||
*/
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* Create an advanced search event.
|
||||
*
|
||||
* @param query Query
|
||||
*/
|
||||
public AdvancedSearchEvent(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of query.
|
||||
*
|
||||
* @return query
|
||||
*/
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package com.sismics.docs.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.adapter.LanguageAdapter;
|
||||
import com.sismics.docs.adapter.TagAutoCompleteAdapter;
|
||||
import com.sismics.docs.event.AdvancedSearchEvent;
|
||||
import com.sismics.docs.ui.view.DatePickerView;
|
||||
import com.sismics.docs.ui.view.TagsCompleteTextView;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
import com.sismics.docs.util.SearchQueryBuilder;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
|
||||
/**
|
||||
* Advanced search fragment.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class SearchFragment extends DialogFragment {
|
||||
/**
|
||||
* Document sharing dialog fragment
|
||||
*/
|
||||
public static SearchFragment newInstance() {
|
||||
SearchFragment fragment = new SearchFragment();
|
||||
Bundle args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
// Setup the view
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.search_dialog, null);
|
||||
final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText);
|
||||
final EditText fulltextEditText = (EditText) view.findViewById(R.id.fulltextEditText);
|
||||
final CheckBox sharedCheckbox = (CheckBox) view.findViewById(R.id.sharedCheckbox);
|
||||
final Spinner languageSpinner = (Spinner) view.findViewById(R.id.languageSpinner);
|
||||
final DatePickerView beforeDatePicker = (DatePickerView) view.findViewById(R.id.beforeDatePicker);
|
||||
final DatePickerView afterDatePicker = (DatePickerView) view.findViewById(R.id.afterDatePicker);
|
||||
final TagsCompleteTextView tagsEditText = (TagsCompleteTextView) view.findViewById(R.id.tagsEditText);
|
||||
|
||||
// Language spinner
|
||||
LanguageAdapter languageAdapter = new LanguageAdapter(getActivity(), true);
|
||||
languageSpinner.setAdapter(languageAdapter);
|
||||
|
||||
// Tags auto-complete
|
||||
JSONObject tags = PreferenceUtil.getCachedJson(getActivity(), PreferenceUtil.PREF_CACHED_TAGS_JSON);
|
||||
if (tags == null) {
|
||||
Dialog dialog = builder.create();
|
||||
dialog.cancel();
|
||||
return dialog;
|
||||
}
|
||||
JSONArray tagArray = tags.optJSONArray("stats");
|
||||
|
||||
List<JSONObject> tagList = new ArrayList<>();
|
||||
for (int i = 0; i < tagArray.length(); i++) {
|
||||
tagList.add(tagArray.optJSONObject(i));
|
||||
}
|
||||
|
||||
tagsEditText.allowDuplicates(false);
|
||||
tagsEditText.setAdapter(new TagAutoCompleteAdapter(getActivity(), 0, tagList));
|
||||
|
||||
// Build the dialog
|
||||
builder.setView(view)
|
||||
.setPositiveButton(R.string.search, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
// Build the simple criterias
|
||||
SearchQueryBuilder queryBuilder = new SearchQueryBuilder()
|
||||
.simpleSearch(searchEditText.getText().toString())
|
||||
.shared(sharedCheckbox.isChecked())
|
||||
.language(((LanguageAdapter.Language) languageSpinner.getSelectedItem()).getId())
|
||||
.before(beforeDatePicker.getDate())
|
||||
.after(afterDatePicker.getDate());
|
||||
|
||||
// Fulltext criteria
|
||||
String fulltextCriteria = fulltextEditText.getText().toString();
|
||||
if (!fulltextCriteria.trim().isEmpty()) {
|
||||
String[] criterias = fulltextCriteria.split(" ");
|
||||
for (String criteria : criterias) {
|
||||
queryBuilder.fulltextSearch(criteria);
|
||||
}
|
||||
}
|
||||
|
||||
// Tags criteria
|
||||
for (Object object : tagsEditText.getObjects()) {
|
||||
JSONObject tag = (JSONObject) object;
|
||||
queryBuilder.tag(tag.optString("name"));
|
||||
}
|
||||
|
||||
// Send the advanced search event
|
||||
EventBus.getDefault().post(new AdvancedSearchEvent(queryBuilder.build()));
|
||||
|
||||
getDialog().cancel();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
getDialog().cancel();
|
||||
}
|
||||
});
|
||||
Dialog dialog = builder.create();
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||
return dialog;
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package com.sismics.docs.util;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Search query builder.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class SearchQueryBuilder {
|
||||
/**
|
||||
* The query.
|
||||
*/
|
||||
private StringBuilder query;
|
||||
|
||||
/**
|
||||
* Search separator.
|
||||
*/
|
||||
private static String SEARCH_SEPARATOR = " ";
|
||||
|
||||
/**
|
||||
* Search date format.
|
||||
*/
|
||||
private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* Build a query.
|
||||
*/
|
||||
public SearchQueryBuilder() {
|
||||
query = new StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simple search criteria.
|
||||
*
|
||||
* @param simpleSearch Simple search criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder simpleSearch(String simpleSearch) {
|
||||
if (isValid(simpleSearch)) {
|
||||
query.append(SEARCH_SEPARATOR).append(simpleSearch);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a fulltext search criteria.
|
||||
*
|
||||
* @param fulltextSearch Fulltext search criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder fulltextSearch(String fulltextSearch) {
|
||||
if (isValid(fulltextSearch)) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("full:")
|
||||
.append(fulltextSearch);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a language criteria.
|
||||
*
|
||||
* @param language Language criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder language(String language) {
|
||||
if (isValid(language)) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("lang:")
|
||||
.append(language);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared criteria.
|
||||
*
|
||||
* @param shared Shared criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder shared(boolean shared) {
|
||||
if (shared) {
|
||||
query.append(SEARCH_SEPARATOR).append("shared:yes");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tag criteria.
|
||||
*
|
||||
* @param tag Tag criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder tag(String tag) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("tag:")
|
||||
.append(tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a before date criteria.
|
||||
*
|
||||
* @param before Before date criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder before(Date before) {
|
||||
if (before != null) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("before:")
|
||||
.append(DATE_FORMAT.format(before));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an after date criteria.
|
||||
*
|
||||
* @param after After date criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder after(Date after) {
|
||||
if (after != null) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("after:")
|
||||
.append(DATE_FORMAT.format(after));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query.
|
||||
*
|
||||
* @return The query
|
||||
*/
|
||||
public String build() {
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the search criteria is valid.
|
||||
*
|
||||
* @param criteria Search criteria
|
||||
* @return True if the search criteria is valid
|
||||
*/
|
||||
private boolean isValid(String criteria) {
|
||||
return criteria != null && !criteria.trim().isEmpty();
|
||||
}
|
||||
}
|
@ -10,8 +10,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:padding="16dp"
|
||||
android:textSize="24dp"
|
||||
android:hint="Title"/>
|
||||
android:textSize="24sp"
|
||||
android:hint="@string/title"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/descriptionEditText"
|
||||
@ -20,7 +20,7 @@
|
||||
android:inputType="textMultiLine"
|
||||
android:layout_margin="8dp"
|
||||
android:padding="16dp"
|
||||
android:hint="Description"
|
||||
android:hint="@string/description"
|
||||
android:textSize="18sp"
|
||||
android:lines="2"/>
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:text="Creation date"
|
||||
android:text="@string/creation_date"
|
||||
android:textColor="#9f9f9f"
|
||||
android:fontFamily="sans-serif"
|
||||
android:layout_width="wrap_content"
|
||||
@ -46,7 +46,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textSize="18sp"
|
||||
android:text="15/11/2014"
|
||||
android:padding="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -63,6 +62,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
android:hint="Add tags"
|
||||
android:hint="@string/add_tags"
|
||||
android:layout_margin="8dp"/>
|
||||
</LinearLayout>
|
@ -2,110 +2,80 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="vertical"
|
||||
android:background="@color/colorPrimaryDark">
|
||||
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
android:id="@+id/loginForm"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="20dp"
|
||||
android:scrollbarStyle="insideOverlay"
|
||||
android:visibility="gone" >
|
||||
android:visibility="visible"
|
||||
android:padding="40dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<ImageView
|
||||
android:contentDescription="@string/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginExplain"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtServer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:hint="@string/server"
|
||||
android:inputType="textNoSuggestions">
|
||||
</EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtUsername"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:hint="@string/username"
|
||||
android:inputType="textNoSuggestions">
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtPassword"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword">
|
||||
</EditText>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnConnect"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="50dip"
|
||||
android:layout_marginTop="20dip"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginExplain"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:text="@string/server" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtServer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:inputType="textNoSuggestions">
|
||||
</EditText>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:text="@string/username" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtUsername"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:inputType="textNoSuggestions" >
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:text="@string/password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/txtPassword"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword" >
|
||||
</EditText>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnConnect"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="50dip"
|
||||
android:layout_marginTop="20dip"
|
||||
android:gravity="center"
|
||||
android:text="@string/login"
|
||||
android:textColor="@android:color/black" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
android:text="@string/login" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center"
|
||||
android:visibility="visible" >
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
|
123
docs-android/app/src/main/res/layout/search_dialog.xml
Normal file
123
docs-android/app/src/main/res/layout/search_dialog.xml
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Simple search -->
|
||||
<EditText
|
||||
android:id="@+id/searchEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textSize="18sp"
|
||||
android:hint="@string/simple_search"/>
|
||||
|
||||
<!-- Fulltext search -->
|
||||
<EditText
|
||||
android:id="@+id/fulltextEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textSize="18sp"
|
||||
android:hint="@string/fulltext_search"/>
|
||||
|
||||
<!-- Language -->
|
||||
<Spinner
|
||||
android:id="@+id/languageSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<!-- Shared -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:text="@string/shared_documents"
|
||||
android:textColor="#9f9f9f"
|
||||
android:fontFamily="sans-serif"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/sharedCheckbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Before date -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_weight="0.5"
|
||||
android:text="@string/after_date"
|
||||
android:textColor="#9f9f9f"
|
||||
android:fontFamily="sans-serif"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<com.sismics.docs.ui.view.DatePickerView
|
||||
style="@android:style/Widget.DeviceDefault.Light.Spinner"
|
||||
android:id="@+id/afterDatePicker"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- After date -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_weight="0.5"
|
||||
android:text="@string/before_date"
|
||||
android:textColor="#9f9f9f"
|
||||
android:fontFamily="sans-serif"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<com.sismics.docs.ui.view.DatePickerView
|
||||
style="@android:style/Widget.DeviceDefault.Light.Spinner"
|
||||
android:id="@+id/beforeDatePicker"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Tags -->
|
||||
<com.sismics.docs.ui.view.TagsCompleteTextView
|
||||
android:id="@+id/tagsEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/search_tags"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -5,7 +5,13 @@
|
||||
<item android:id="@+id/action_search"
|
||||
android:title="@string/action_search"
|
||||
app:showAsAction="ifRoom"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView" />
|
||||
app:actionViewClass="android.widget.SearchView" />
|
||||
|
||||
<item
|
||||
android:id="@+id/advanced_search"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/advanced_search">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
|
6
docs-android/app/src/main/res/values/colors.xml
Normal file
6
docs-android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#263238</color>
|
||||
<color name="colorPrimaryDark">#21272b</color>
|
||||
<color name="colorAccent">#009688</color>
|
||||
</resources>
|
@ -93,6 +93,18 @@
|
||||
<string name="upload_notification_message">Uploading the new file to the document</string>
|
||||
<string name="upload_notification_error">Error uploading the new file</string>
|
||||
<string name="delete_file">Delete current file</string>
|
||||
<string name="advanced_search">Advanced Search</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="add_tags">Add tags</string>
|
||||
<string name="creation_date">Creation date</string>
|
||||
<string name="description">Description</string>
|
||||
<string name="title">Title</string>
|
||||
<string name="simple_search">Simple search</string>
|
||||
<string name="fulltext_search">Fulltext search</string>
|
||||
<string name="after_date">After date</string>
|
||||
<string name="before_date">Before date</string>
|
||||
<string name="search_tags">Search tags</string>
|
||||
<string name="all_languages">All languages</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -1,10 +1,15 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<item name="colorPrimary">#263238</item>
|
||||
<item name="colorPrimaryDark">#21272b</item>
|
||||
<item name="colorAccent">#009688</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeDark" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.sismics.docs.core.constant;
|
||||
|
||||
/**
|
||||
* ACL target types.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public enum AclTargetType {
|
||||
/**
|
||||
* An user.
|
||||
*/
|
||||
USER,
|
||||
|
||||
/**
|
||||
* A group.
|
||||
*/
|
||||
GROUP,
|
||||
|
||||
/**
|
||||
* A share.
|
||||
*/
|
||||
SHARE
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.sismics.docs.core.constant;
|
||||
|
||||
/**
|
||||
* Permissions.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public enum PermType {
|
||||
/**
|
||||
* Read document.
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* Write document.
|
||||
*/
|
||||
WRITE
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.sismics.docs.core.dao.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
* ACL DAO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AclDao {
|
||||
/**
|
||||
* Creates a new ACL.
|
||||
*
|
||||
* @param acl ACL
|
||||
* @return New ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public String create(Acl acl) {
|
||||
// Create the UUID
|
||||
acl.setId(UUID.randomUUID().toString());
|
||||
|
||||
// Create the ACL
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.persist(acl);
|
||||
|
||||
return acl.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search ACLs by target ID.
|
||||
*
|
||||
* @param targetId Target ID
|
||||
* @return ACL list
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Acl> getByTargetId(String targetId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select a from Acl a where a.targetId = :targetId and a.deleteDate is null");
|
||||
q.setParameter("targetId", targetId);
|
||||
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search ACLs by source ID.
|
||||
*
|
||||
* @param sourceId Source ID
|
||||
* @return ACL DTO list
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<AclDto> getBySourceId(String sourceId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, u.USE_USERNAME_C, s.SHA_NAME_C");
|
||||
sb.append(" from T_ACL a ");
|
||||
sb.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C ");
|
||||
sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ");
|
||||
sb.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId ");
|
||||
|
||||
// Perform the query
|
||||
Query q = em.createNativeQuery(sb.toString());
|
||||
q.setParameter("sourceId", sourceId);
|
||||
List<Object[]> l = q.getResultList();
|
||||
|
||||
// Assemble results
|
||||
List<AclDto> aclDtoList = new ArrayList<AclDto>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
AclDto aclDto = new AclDto();
|
||||
aclDto.setId((String) o[i++]);
|
||||
aclDto.setPerm(PermType.valueOf((String) o[i++]));
|
||||
aclDto.setTargetId((String) o[i++]);
|
||||
String userName = (String) o[i++];
|
||||
String shareName = (String) o[i++];
|
||||
aclDto.setTargetName(userName == null ? shareName : userName);
|
||||
aclDto.setTargetType(userName == null ?
|
||||
AclTargetType.SHARE.name() : AclTargetType.USER.name());
|
||||
aclDtoList.add(aclDto);
|
||||
}
|
||||
return aclDtoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a source is accessible to a target.
|
||||
*
|
||||
* @param sourceId ACL source entity ID
|
||||
* @parm perm Necessary permission
|
||||
* @param targetId ACL target entity ID
|
||||
* @return True if the document is accessible
|
||||
*/
|
||||
public boolean checkPermission(String sourceId, PermType perm, String targetId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.deleteDate is null");
|
||||
q.setParameter("sourceId", sourceId);
|
||||
q.setParameter("perm", perm);
|
||||
q.setParameter("targetId", targetId);
|
||||
|
||||
// We have a matching permission
|
||||
if (q.getResultList().size() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an ACL.
|
||||
*
|
||||
* @param sourceId Source ID
|
||||
* @param perm Permission
|
||||
* @param targetId Target ID
|
||||
*/
|
||||
public void delete(String sourceId, PermType perm, String targetId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
|
||||
q.setParameter("sourceId", sourceId);
|
||||
q.setParameter("perm", perm);
|
||||
q.setParameter("targetId", targetId);
|
||||
q.setParameter("dateNow", new Date());
|
||||
q.executeUpdate();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import javax.persistence.Query;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.dao.lucene.LuceneDao;
|
||||
@ -78,13 +79,17 @@ public class DocumentDao {
|
||||
* Returns an active document.
|
||||
*
|
||||
* @param id Document ID
|
||||
* @param perm Permission needed
|
||||
* @param userId User ID
|
||||
* @return Document
|
||||
*/
|
||||
public Document getDocument(String id, String userId) {
|
||||
public Document getDocument(String id, PermType perm, String userId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select d from Document d where d.id = :id and d.userId = :userId and d.deleteDate is null");
|
||||
Query q = em.createNativeQuery("select d.* from T_DOCUMENT d "
|
||||
+ " join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "
|
||||
+ " where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null", Document.class);
|
||||
q.setParameter("id", id);
|
||||
q.setParameter("perm", perm.name());
|
||||
q.setParameter("userId", userId);
|
||||
return (Document) q.getSingleResult();
|
||||
}
|
||||
@ -112,7 +117,13 @@ public class DocumentDao {
|
||||
q.setParameter("dateNow", dateNow);
|
||||
q.executeUpdate();
|
||||
|
||||
q = em.createQuery("update Share s set s.deleteDate = :dateNow where s.documentId = :documentId and s.deleteDate is null");
|
||||
// TODO Delete share from deleted ACLs
|
||||
// q = em.createQuery("update Share s set s.deleteDate = :dateNow where s.documentId = :documentId and s.deleteDate is null");
|
||||
// q.setParameter("documentId", id);
|
||||
// q.setParameter("dateNow", dateNow);
|
||||
// q.executeUpdate();
|
||||
|
||||
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :documentId");
|
||||
q.setParameter("documentId", id);
|
||||
q.setParameter("dateNow", dateNow);
|
||||
q.executeUpdate();
|
||||
@ -145,19 +156,20 @@ public class DocumentDao {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, s.SHA_ID_C is not null c5, ");
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, ");
|
||||
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) c5, ");
|
||||
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6 ");
|
||||
sb.append(" from T_DOCUMENT d ");
|
||||
sb.append(" left join T_SHARE s on s.SHA_IDDOCUMENT_C = d.DOC_ID_C and s.SHA_DELETEDATE_D is null ");
|
||||
|
||||
// Adds search criteria
|
||||
if (criteria.getUserId() != null) {
|
||||
criteriaList.add("d.DOC_IDUSER_C = :userId");
|
||||
// Read permission is enough for searching
|
||||
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
|
||||
parameterMap.put("userId", criteria.getUserId());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
|
||||
LuceneDao luceneDao = new LuceneDao();
|
||||
Set<String> documentIdList = luceneDao.search(criteria.getUserId(), criteria.getSearch(), criteria.getFullSearch());
|
||||
Set<String> documentIdList = luceneDao.search(criteria.getSearch(), criteria.getFullSearch());
|
||||
if (documentIdList.size() == 0) {
|
||||
// If the search doesn't find any document, the request should return nothing
|
||||
documentIdList.add(UUID.randomUUID().toString());
|
||||
@ -183,7 +195,7 @@ public class DocumentDao {
|
||||
}
|
||||
}
|
||||
if (criteria.getShared() != null && criteria.getShared()) {
|
||||
criteriaList.add("s.SHA_ID_C is not null");
|
||||
criteriaList.add("(select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) > 0");
|
||||
}
|
||||
if (criteria.getLanguage() != null) {
|
||||
criteriaList.add("d.DOC_LANGUAGE_C = :language");
|
||||
@ -211,7 +223,7 @@ public class DocumentDao {
|
||||
documentDto.setDescription((String) o[i++]);
|
||||
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
documentDto.setLanguage((String) o[i++]);
|
||||
documentDto.setShared((Boolean) o[i++]);
|
||||
documentDto.setShared(((Number) o[i++]).intValue() > 0);
|
||||
documentDto.setFileCount(((Number) o[i++]).intValue());
|
||||
documentDtoList.add(documentDto);
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
package com.sismics.docs.core.dao.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.Share;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
@ -37,23 +34,6 @@ public class ShareDao {
|
||||
return share.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an active share.
|
||||
*
|
||||
* @param id Share ID
|
||||
* @return Document
|
||||
*/
|
||||
public Share getShare(String id) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select s from Share s where s.id = :id and s.deleteDate is null");
|
||||
q.setParameter("id", id);
|
||||
try {
|
||||
return (Share) q.getSingleResult();
|
||||
} catch (NoResultException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a share.
|
||||
*
|
||||
@ -70,44 +50,11 @@ public class ShareDao {
|
||||
// Delete the share
|
||||
Date dateNow = new Date();
|
||||
shareDb.setDeleteDate(dateNow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shares by document ID.
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @return List of shares
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Share> getByDocumentId(String documentId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select s from Share s where s.documentId = :documentId and s.deleteDate is null");
|
||||
q.setParameter("documentId", documentId);
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a document is visible.
|
||||
*
|
||||
* @param document Document to check for visibility
|
||||
* @param userId Optional user trying to access the document
|
||||
* @param shareId Optional share to access the document
|
||||
* @return True if the document is visible
|
||||
*/
|
||||
public boolean checkVisibility(Document document, String userId, String shareId) {
|
||||
// The user owns the document
|
||||
if (document.getUserId().equals(userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The share is linked to the document
|
||||
if (shareId != null) {
|
||||
Share share = getShare(shareId);
|
||||
if (share != null && share.getDocumentId().equals(document.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// Delete the linked ACL
|
||||
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.targetId = :targetId");
|
||||
q.setParameter("targetId", id);
|
||||
q.setParameter("dateNow", dateNow);
|
||||
q.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,22 @@
|
||||
package com.sismics.docs.core.dao.jpa;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
@ -9,13 +24,6 @@ import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.QueryParam;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* User DAO.
|
||||
@ -204,13 +212,19 @@ public class UserDao {
|
||||
* @param paginatedList List of users (updated by side effects)
|
||||
* @param sortCriteria Sort criteria
|
||||
*/
|
||||
public void findAll(PaginatedList<UserDto> paginatedList, SortCriteria sortCriteria) {
|
||||
public void findByCriteria(PaginatedList<UserDto> paginatedList, UserCriteria criteria, SortCriteria sortCriteria) {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_IDLOCALE_C as c4");
|
||||
sb.append(" from T_USER u ");
|
||||
|
||||
// Add search criterias
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
if (criteria.getSearch() != null) {
|
||||
criteriaList.add("lower(u.USE_USERNAME_C) like lower(:search)");
|
||||
parameterMap.put("search", "%" + criteria.getSearch() + "%");
|
||||
}
|
||||
|
||||
criteriaList.add("u.USE_DELETEDATE_D is null");
|
||||
|
||||
if (!criteriaList.isEmpty()) {
|
||||
|
@ -0,0 +1,34 @@
|
||||
package com.sismics.docs.core.dao.jpa.criteria;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* User criteria.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class UserCriteria {
|
||||
/**
|
||||
* Search query.
|
||||
*/
|
||||
private String search;
|
||||
|
||||
/**
|
||||
* Getter of search.
|
||||
*
|
||||
* @return the search
|
||||
*/
|
||||
public String getSearch() {
|
||||
return search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of search.
|
||||
*
|
||||
* @param search search
|
||||
*/
|
||||
public UserCriteria setSearch(String search) {
|
||||
this.search = search;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.sismics.docs.core.dao.jpa.dto;
|
||||
|
||||
import javax.persistence.Id;
|
||||
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
|
||||
/**
|
||||
* Acl DTO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AclDto {
|
||||
/**
|
||||
* Acl ID.
|
||||
*/
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Target name.
|
||||
*/
|
||||
private String targetName;
|
||||
|
||||
/**
|
||||
* Permission.
|
||||
*/
|
||||
private PermType perm;
|
||||
|
||||
/**
|
||||
* Source ID.
|
||||
*/
|
||||
private String sourceId;
|
||||
|
||||
/**
|
||||
* Target ID.
|
||||
*/
|
||||
private String targetId;
|
||||
|
||||
/**
|
||||
* Target type.
|
||||
*/
|
||||
private String targetType;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTargetName() {
|
||||
return targetName;
|
||||
}
|
||||
|
||||
public void setTargetName(String targetName) {
|
||||
this.targetName = targetName;
|
||||
}
|
||||
|
||||
public PermType getPerm() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
public void setPerm(PermType perm) {
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public String getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(String sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public String getTargetId() {
|
||||
return targetId;
|
||||
}
|
||||
|
||||
public void setTargetId(String targetId) {
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
public String getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
public void setTargetType(String targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package com.sismics.docs.core.dao.lucene;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -13,7 +12,6 @@ import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.queries.TermsFilter;
|
||||
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
|
||||
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
|
||||
import org.apache.lucene.search.BooleanClause.Occur;
|
||||
@ -143,13 +141,12 @@ public class LuceneDao {
|
||||
/**
|
||||
* Search files.
|
||||
*
|
||||
* @param userId User ID to filter on
|
||||
* @param searchQuery Search query on title and description
|
||||
* @param fullSearchQuery Search query on all fields
|
||||
* @return List of document IDs
|
||||
* @throws Exception
|
||||
*/
|
||||
public Set<String> search(String userId, String searchQuery, String fullSearchQuery) throws Exception {
|
||||
public Set<String> search(String searchQuery, String fullSearchQuery) throws Exception {
|
||||
// Escape query and add quotes so QueryParser generate a PhraseQuery
|
||||
searchQuery = "\"" + QueryParserUtil.escape(searchQuery + " " + fullSearchQuery) + "\"";
|
||||
fullSearchQuery = "\"" + QueryParserUtil.escape(fullSearchQuery) + "\"";
|
||||
@ -164,13 +161,6 @@ public class LuceneDao {
|
||||
query.add(qpHelper.parse(searchQuery, "description"), Occur.SHOULD);
|
||||
query.add(qpHelper.parse(fullSearchQuery, "content"), Occur.SHOULD);
|
||||
|
||||
// Filter on provided user ID
|
||||
List<Term> terms = new ArrayList<Term>();
|
||||
if (userId != null) {
|
||||
terms.add(new Term("user_id", userId));
|
||||
}
|
||||
TermsFilter userFilter = new TermsFilter(terms);
|
||||
|
||||
// Search
|
||||
DirectoryReader directoryReader = AppContext.getInstance().getIndexingService().getDirectoryReader();
|
||||
Set<String> documentIdList = new HashSet<String>();
|
||||
@ -179,7 +169,7 @@ public class LuceneDao {
|
||||
return documentIdList;
|
||||
}
|
||||
IndexSearcher searcher = new IndexSearcher(directoryReader);
|
||||
TopDocs topDocs = searcher.search(query, userFilter, Integer.MAX_VALUE);
|
||||
TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE);
|
||||
ScoreDoc[] docs = topDocs.scoreDocs;
|
||||
|
||||
// Extract document IDs
|
||||
@ -207,7 +197,6 @@ public class LuceneDao {
|
||||
private org.apache.lucene.document.Document getDocumentFromDocument(Document document) {
|
||||
org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
|
||||
luceneDocument.add(new StringField("id", document.getId(), Field.Store.YES));
|
||||
luceneDocument.add(new StringField("user_id", document.getUserId(), Field.Store.YES));
|
||||
luceneDocument.add(new StringField("type", "document", Field.Store.YES));
|
||||
if (document.getTitle() != null) {
|
||||
luceneDocument.add(new TextField("title", document.getTitle(), Field.Store.NO));
|
||||
@ -229,7 +218,6 @@ public class LuceneDao {
|
||||
private org.apache.lucene.document.Document getDocumentFromFile(File file, Document document) {
|
||||
org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
|
||||
luceneDocument.add(new StringField("id", file.getId(), Field.Store.YES));
|
||||
luceneDocument.add(new StringField("user_id", document.getUserId(), Field.Store.YES));
|
||||
luceneDocument.add(new StringField("type", "file", Field.Store.YES));
|
||||
luceneDocument.add(new StringField("document_id", file.getDocumentId(), Field.Store.YES));
|
||||
if (file.getContent() != null) {
|
||||
|
104
docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java
Normal file
104
docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java
Normal file
@ -0,0 +1,104 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
|
||||
/**
|
||||
* ACL entity.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "T_ACL")
|
||||
public class Acl {
|
||||
/**
|
||||
* ACL ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "ACL_ID_C", length = 36)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* ACL permission.
|
||||
*/
|
||||
@Column(name = "ACL_PERM_C", length = 30)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private PermType perm;
|
||||
|
||||
/**
|
||||
* ACL source ID.
|
||||
*/
|
||||
@Column(name = "ACL_SOURCEID_C", length = 36)
|
||||
private String sourceId;
|
||||
|
||||
/**
|
||||
* ACL target ID.
|
||||
*/
|
||||
@Column(name = "ACL_TARGETID_C", length = 36)
|
||||
private String targetId;
|
||||
|
||||
/**
|
||||
* Deletion date.
|
||||
*/
|
||||
@Column(name = "ACL_DELETEDATE_D")
|
||||
private Date deleteDate;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public PermType getPerm() {
|
||||
return perm;
|
||||
}
|
||||
|
||||
public void setPerm(PermType perm) {
|
||||
this.perm = perm;
|
||||
}
|
||||
|
||||
public String getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(String sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public String getTargetId() {
|
||||
return targetId;
|
||||
}
|
||||
|
||||
public void setTargetId(String targetId) {
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
|
||||
public void setDeleteDate(Date deleteDate) {
|
||||
this.deleteDate = deleteDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("perm", perm)
|
||||
.add("sourceId", sourceId)
|
||||
.add("targetId", targetId)
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* File share.
|
||||
* ACL target used to share a document.
|
||||
* Can only be used on a single ACL
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@ -26,12 +27,6 @@ public class Share {
|
||||
@Column(name = "SHA_NAME_C", length = 36)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
@Column(name = "SHA_IDDOCUMENT_C", nullable = false, length = 36)
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* Creation date.
|
||||
*/
|
||||
@ -80,24 +75,6 @@ public class Share {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of documentId.
|
||||
*
|
||||
* @return the documentId
|
||||
*/
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of documentId.
|
||||
*
|
||||
* @param documentId documentId
|
||||
*/
|
||||
public void setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of createDate.
|
||||
*
|
||||
@ -138,7 +115,6 @@ public class Share {
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("tagId", documentId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
@ -16,5 +16,6 @@
|
||||
<class>com.sismics.docs.core.model.jpa.Tag</class>
|
||||
<class>com.sismics.docs.core.model.jpa.DocumentTag</class>
|
||||
<class>com.sismics.docs.core.model.jpa.Share</class>
|
||||
<class>com.sismics.docs.core.model.jpa.Acl</class>
|
||||
</persistence-unit>
|
||||
</persistence>
|
@ -1 +1 @@
|
||||
db.version=7
|
||||
db.version=8
|
@ -0,0 +1,4 @@
|
||||
create cached table T_ACL ( ACL_ID_C varchar(36) not null, ACL_PERM_C varchar(30) not null, ACL_SOURCEID_C varchar(36) not null, ACL_TARGETID_C varchar(36) not null, ACL_DELETEDATE_D datetime, primary key (ACL_ID_C) );
|
||||
drop table T_SHARE;
|
||||
create cached table T_SHARE ( SHA_ID_C varchar(36) not null, SHA_NAME_C varchar(36), SHA_CREATEDATE_D datetime, SHA_DELETEDATE_D datetime, primary key (SHA_ID_C) );
|
||||
update T_CONFIG set CFG_VALUE_C='8' where CFG_ID_C='DB_VERSION';
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=7
|
||||
db.version=8
|
@ -0,0 +1,174 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
|
||||
/**
|
||||
* ACL REST resources.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Path("/acl")
|
||||
public class AclResource extends BaseResource {
|
||||
/**
|
||||
* Add an ACL.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@PUT
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response add(@FormParam("source") String sourceId,
|
||||
@FormParam("perm") String permStr,
|
||||
@FormParam("username") String username) throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Validate input
|
||||
sourceId = ValidationUtil.validateLength(sourceId, "source", 36, 36, false);
|
||||
PermType perm = PermType.valueOf(ValidationUtil.validateLength(permStr, "perm", 1, 30, false));
|
||||
username = ValidationUtil.validateLength(username, "username", 1, 50, false);
|
||||
|
||||
// Validate the target user
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getActiveByUsername(username);
|
||||
if (user == null) {
|
||||
throw new ClientException("UserNotFound", MessageFormat.format("User not found: {0}", username));
|
||||
}
|
||||
|
||||
// Check permission on the source by the principal
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(sourceId, PermType.WRITE, principal.getId())) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Create the ACL
|
||||
Acl acl = new Acl();
|
||||
acl.setSourceId(sourceId);
|
||||
acl.setPerm(perm);
|
||||
acl.setTargetId(user.getId());
|
||||
|
||||
// Avoid duplicates
|
||||
if (!aclDao.checkPermission(acl.getSourceId(), acl.getPerm(), acl.getTargetId())) {
|
||||
aclDao.create(acl);
|
||||
|
||||
// Returns the ACL
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("perm", acl.getPerm().name());
|
||||
response.put("id", acl.getTargetId());
|
||||
response.put("name", user.getUsername());
|
||||
response.put("type", AclTargetType.USER.name());
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
return Response.ok().entity(new JSONObject()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an ACL.
|
||||
*
|
||||
* @param id ACL ID
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{sourceId: [a-z0-9\\-]+}/{perm: READ|WRITE}/{targetId: [a-z0-9\\-]+}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response delete(
|
||||
@PathParam("sourceId") String sourceId,
|
||||
@PathParam("perm") String permStr,
|
||||
@PathParam("targetId") String targetId) throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Validate input
|
||||
sourceId = ValidationUtil.validateLength(sourceId, "source", 36, 36, false);
|
||||
PermType perm = PermType.valueOf(ValidationUtil.validateLength(permStr, "perm", 1, 30, false));
|
||||
targetId = ValidationUtil.validateLength(targetId, "target", 36, 36, false);
|
||||
|
||||
// Check permission on the source by the principal
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(sourceId, PermType.WRITE, principal.getId())) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Cannot delete R/W on a source document if the target is the creator
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Document document = documentDao.getById(sourceId);
|
||||
if (document != null && document.getUserId().equals(targetId)) {
|
||||
throw new ClientException("AclError", "Cannot delete base ACL on a document");
|
||||
}
|
||||
|
||||
// Delete the ACL
|
||||
aclDao.delete(sourceId, perm, targetId);
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("target/search")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response targetList(@QueryParam("search") String search) throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Validate input
|
||||
search = ValidationUtil.validateLength(search, "search", 1, 50, false);
|
||||
|
||||
// Search users
|
||||
UserDao userDao = new UserDao();
|
||||
JSONObject response = new JSONObject();
|
||||
List<JSONObject> users = new ArrayList<>();
|
||||
|
||||
PaginatedList<UserDto> paginatedList = PaginatedLists.create();
|
||||
SortCriteria sortCriteria = new SortCriteria(1, true);
|
||||
|
||||
userDao.findByCriteria(paginatedList, new UserCriteria().setSearch(search), sortCriteria);
|
||||
for (UserDto userDto : paginatedList.getResultList()) {
|
||||
JSONObject user = new JSONObject();
|
||||
user.put("username", userDto.getUsername());
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
response.put("users", users);
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
@ -20,11 +22,14 @@ import org.apache.log4j.Logger;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
@ -34,6 +39,7 @@ import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.docs.rest.constant.BaseFunction;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.exception.ServerException;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.log4j.LogCriteria;
|
||||
import com.sismics.util.log4j.LogEntry;
|
||||
import com.sismics.util.log4j.MemoryAppender;
|
||||
@ -206,4 +212,58 @@ public class AppResource extends BaseResource {
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild ACLs.
|
||||
* Set Read + Write on documents' creator.
|
||||
* Loose all sharing.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/rebuild_acls")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchRebuildAcls() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
AclDao aclDao = new AclDao();
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.createNativeQuery("truncate table T_ACL").executeUpdate();
|
||||
em.createNativeQuery("truncate table T_SHARE").executeUpdate();
|
||||
|
||||
Query q = em.createNativeQuery("select DOC_ID_C, DOC_IDUSER_C from T_DOCUMENT");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object[]> l = q.getResultList();
|
||||
for (Object[] o : l) {
|
||||
String documentId = (String) o[0];
|
||||
String userId = (String) o[1];
|
||||
|
||||
// Create read ACL
|
||||
Acl acl = new Acl();
|
||||
acl.setPerm(PermType.READ);
|
||||
acl.setSourceId(documentId);
|
||||
acl.setTargetId(userId);
|
||||
System.out.println(acl);
|
||||
aclDao.create(acl);
|
||||
|
||||
// Create write ACL
|
||||
acl = new Acl();
|
||||
acl.setPerm(PermType.WRITE);
|
||||
acl.setSourceId(documentId);
|
||||
acl.setTargetId(userId);
|
||||
System.out.println(acl);
|
||||
aclDao.create(acl);
|
||||
}
|
||||
|
||||
int mod = em.createNativeQuery("update T_FILE set FIL_IDUSER_C = (select DOC_IDUSER_C from T_DOCUMENT where DOC_ID_C = FIL_IDDOC_C) where FIL_IDDOC_C is not null").executeUpdate();
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
response.put("file_id_user_updated", mod);
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
||||
|
@ -33,11 +33,14 @@ import org.joda.time.format.DateTimeParser;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.TagDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
|
||||
@ -45,9 +48,9 @@ import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.Share;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
@ -67,7 +70,7 @@ public class DocumentResource extends BaseResource {
|
||||
/**
|
||||
* Returns a document.
|
||||
*
|
||||
* @param id Document ID
|
||||
* @param documentId Document ID
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@ -75,22 +78,23 @@ public class DocumentResource extends BaseResource {
|
||||
@Path("{id: [a-z0-9\\-]+}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response get(
|
||||
@PathParam("id") String id,
|
||||
@PathParam("id") String documentId,
|
||||
@QueryParam("share") String shareId) throws JSONException {
|
||||
authenticate();
|
||||
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
ShareDao shareDao = new ShareDao();
|
||||
AclDao aclDao = new AclDao();
|
||||
UserDao userDao = new UserDao();
|
||||
Document documentDb;
|
||||
try {
|
||||
documentDb = documentDao.getDocument(id);
|
||||
documentDb = documentDao.getDocument(documentId);
|
||||
|
||||
// Check document visibility
|
||||
if (!shareDao.checkVisibility(documentDb, principal.getId(), shareId)) {
|
||||
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
|
||||
JSONObject document = new JSONObject();
|
||||
@ -99,10 +103,11 @@ public class DocumentResource extends BaseResource {
|
||||
document.put("description", documentDb.getDescription());
|
||||
document.put("create_date", documentDb.getCreateDate().getTime());
|
||||
document.put("language", documentDb.getLanguage());
|
||||
document.put("creator", userDao.getById(documentDb.getUserId()).getUsername());
|
||||
|
||||
// Add tags
|
||||
TagDao tagDao = new TagDao();
|
||||
List<TagDto> tagDtoList = tagDao.getByDocumentId(id);
|
||||
List<TagDto> tagDtoList = tagDao.getByDocumentId(documentId);
|
||||
List<JSONObject> tags = new ArrayList<>();
|
||||
for (TagDto tagDto : tagDtoList) {
|
||||
JSONObject tag = new JSONObject();
|
||||
@ -113,16 +118,24 @@ public class DocumentResource extends BaseResource {
|
||||
}
|
||||
document.put("tags", tags);
|
||||
|
||||
// Add shares
|
||||
List<Share> shareDbList = shareDao.getByDocumentId(id);
|
||||
List<JSONObject> shareList = new ArrayList<>();
|
||||
for (Share shareDb : shareDbList) {
|
||||
JSONObject share = new JSONObject();
|
||||
share.put("id", shareDb.getId());
|
||||
share.put("name", shareDb.getName());
|
||||
shareList.add(share);
|
||||
// Add ACL
|
||||
List<AclDto> aclDtoList = aclDao.getBySourceId(documentId);
|
||||
List<JSONObject> aclList = new ArrayList<>();
|
||||
boolean writable = false;
|
||||
for (AclDto aclDto : aclDtoList) {
|
||||
JSONObject acl = new JSONObject();
|
||||
acl.put("perm", aclDto.getPerm().name());
|
||||
acl.put("id", aclDto.getTargetId());
|
||||
acl.put("name", aclDto.getTargetName());
|
||||
acl.put("type", aclDto.getTargetType());
|
||||
aclList.add(acl);
|
||||
|
||||
if (aclDto.getTargetId().equals(principal.getId()) && aclDto.getPerm() == PermType.WRITE) {
|
||||
writable = true;
|
||||
}
|
||||
}
|
||||
document.put("shares", shareList);
|
||||
document.put("acls", aclList);
|
||||
document.put("writable", writable);
|
||||
|
||||
return Response.ok().entity(document).build();
|
||||
}
|
||||
@ -341,6 +354,21 @@ public class DocumentResource extends BaseResource {
|
||||
}
|
||||
String documentId = documentDao.create(document);
|
||||
|
||||
// Create read ACL
|
||||
AclDao aclDao = new AclDao();
|
||||
Acl acl = new Acl();
|
||||
acl.setPerm(PermType.READ);
|
||||
acl.setSourceId(documentId);
|
||||
acl.setTargetId(principal.getId());
|
||||
aclDao.create(acl);
|
||||
|
||||
// Create write ACL
|
||||
acl = new Acl();
|
||||
acl.setPerm(PermType.WRITE);
|
||||
acl.setSourceId(documentId);
|
||||
acl.setTargetId(principal.getId());
|
||||
aclDao.create(acl);
|
||||
|
||||
// Update tags
|
||||
updateTagList(documentId, tagList);
|
||||
|
||||
@ -389,7 +417,7 @@ public class DocumentResource extends BaseResource {
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Document document;
|
||||
try {
|
||||
document = documentDao.getDocument(id, principal.getId());
|
||||
document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
}
|
||||
@ -470,7 +498,7 @@ public class DocumentResource extends BaseResource {
|
||||
Document document;
|
||||
List<File> fileList;
|
||||
try {
|
||||
document = documentDao.getDocument(id, principal.getId());
|
||||
document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
|
||||
fileList = fileDao.getByDocumentId(principal.getId(), id);
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
|
@ -36,9 +36,10 @@ import org.codehaus.jettison.json.JSONObject;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
@ -97,7 +98,7 @@ public class FileResource extends BaseResource {
|
||||
} else {
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
try {
|
||||
document = documentDao.getDocument(documentId, principal.getId());
|
||||
document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
@ -194,7 +195,7 @@ public class FileResource extends BaseResource {
|
||||
File file;
|
||||
try {
|
||||
file = fileDao.getFile(id, principal.getId());
|
||||
document = documentDao.getDocument(documentId, principal.getId());
|
||||
document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
@ -254,7 +255,7 @@ public class FileResource extends BaseResource {
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
try {
|
||||
documentDao.getDocument(documentId, principal.getId());
|
||||
documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
@ -293,10 +294,8 @@ public class FileResource extends BaseResource {
|
||||
// Check document visibility
|
||||
if (documentId != null) {
|
||||
try {
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Document document = documentDao.getDocument(documentId);
|
||||
ShareDao shareDao = new ShareDao();
|
||||
if (!shareDao.checkVisibility(document, principal.getId(), shareId)) {
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
@ -354,7 +353,7 @@ public class FileResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} else {
|
||||
documentDao.getDocument(file.getDocumentId(), principal.getId());
|
||||
documentDao.getDocument(file.getDocumentId(), PermType.WRITE, principal.getId());
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
|
||||
@ -398,11 +397,8 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Get the file
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
UserDao userDao = new UserDao();
|
||||
File file;
|
||||
Document document;
|
||||
String userId;
|
||||
try {
|
||||
file = fileDao.getFile(fileId);
|
||||
|
||||
@ -412,16 +408,10 @@ public class FileResource extends BaseResource {
|
||||
// But not ours
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
userId = file.getUserId();
|
||||
} else {
|
||||
// It's a file linked to a document
|
||||
document = documentDao.getDocument(file.getDocumentId());
|
||||
userId = document.getUserId();
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
if (!shareDao.checkVisibility(document, principal.getId(), shareId)) {
|
||||
// Check document accessibility
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, shareId == null ? principal.getId() : shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
}
|
||||
@ -451,7 +441,8 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Stream the output and decrypt it if necessary
|
||||
StreamingOutput stream;
|
||||
User user = userDao.getById(userId);
|
||||
// A file is always encrypted by the creator of it
|
||||
User user = userDao.getById(file.getUserId());
|
||||
try {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
final InputStream responseInputStream = decrypt ?
|
||||
@ -500,8 +491,8 @@ public class FileResource extends BaseResource {
|
||||
document = documentDao.getDocument(documentId);
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
if (!shareDao.checkVisibility(document, principal.getId(), shareId)) {
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
@ -510,9 +501,8 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Get files and user associated with this document
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
final UserDao userDao = new UserDao();
|
||||
final List<File> fileList = fileDao.getByDocumentId(principal.getId(), documentId);
|
||||
final User user = userDao.getById(document.getUserId());
|
||||
|
||||
// Create the ZIP stream
|
||||
StreamingOutput stream = new StreamingOutput() {
|
||||
@ -526,6 +516,8 @@ public class FileResource extends BaseResource {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
|
||||
// Add the decrypted file to the ZIP stream
|
||||
// Files are encrypted by the creator of them
|
||||
User user = userDao.getById(file.getUserId());
|
||||
try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) {
|
||||
ZipEntry zipEntry = new ZipEntry(index + "." + MimeTypeUtil.getFileExtension(file.getMimeType()));
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource;
|
||||
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.ws.rs.DELETE;
|
||||
@ -16,8 +17,12 @@ import javax.ws.rs.core.Response;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.model.jpa.Share;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
@ -53,7 +58,7 @@ public class ShareResource extends BaseResource {
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
try {
|
||||
documentDao.getDocument(documentId, principal.getId());
|
||||
documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
@ -61,14 +66,23 @@ public class ShareResource extends BaseResource {
|
||||
// Create the share
|
||||
ShareDao shareDao = new ShareDao();
|
||||
Share share = new Share();
|
||||
share.setDocumentId(documentId);
|
||||
share.setName(name);
|
||||
shareDao.create(share);
|
||||
|
||||
// Create the ACL
|
||||
AclDao aclDao = new AclDao();
|
||||
Acl acl = new Acl();
|
||||
acl.setSourceId(documentId);
|
||||
acl.setPerm(PermType.READ);
|
||||
acl.setTargetId(share.getId());
|
||||
aclDao.create(acl);
|
||||
|
||||
// Always return ok
|
||||
// Returns the created ACL
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
response.put("id", share.getId());
|
||||
response.put("perm", acl.getPerm().name());
|
||||
response.put("id", acl.getTargetId());
|
||||
response.put("name", name);
|
||||
response.put("type", AclTargetType.SHARE);
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
@ -88,23 +102,21 @@ public class ShareResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Get the share
|
||||
ShareDao shareDao = new ShareDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Share share = shareDao.getShare(id);
|
||||
if (share == null) {
|
||||
// Check that the user can share the linked document
|
||||
AclDao aclDao = new AclDao();
|
||||
List<Acl> aclList = aclDao.getByTargetId(id);
|
||||
if (aclList.size() == 0) {
|
||||
throw new ClientException("ShareNotFound", MessageFormat.format("Share not found: {0}", id));
|
||||
}
|
||||
|
||||
// Check that the user is the owner of the linked document
|
||||
try {
|
||||
documentDao.getDocument(share.getDocumentId(), principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", share.getDocumentId()));
|
||||
Acl acl = aclList.get(0);
|
||||
if (!aclDao.checkPermission(acl.getSourceId(), PermType.WRITE, principal.getId())) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", acl.getSourceId()));
|
||||
}
|
||||
|
||||
// Delete the share
|
||||
shareDao.delete(share.getId());
|
||||
ShareDao shareDao = new ShareDao();
|
||||
shareDao.delete(id);
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
|
@ -4,6 +4,7 @@ import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
|
||||
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.AuthenticationToken;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
@ -19,6 +20,7 @@ import com.sismics.rest.util.ValidationUtil;
|
||||
import com.sismics.security.UserPrincipal;
|
||||
import com.sismics.util.LocaleUtil;
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
@ -517,7 +519,7 @@ public class UserResource extends BaseResource {
|
||||
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
|
||||
|
||||
UserDao userDao = new UserDao();
|
||||
userDao.findAll(paginatedList, sortCriteria);
|
||||
userDao.findByCriteria(paginatedList, new UserCriteria(), sortCriteria);
|
||||
for (UserDto userDto : paginatedList.getResultList()) {
|
||||
JSONObject user = new JSONObject();
|
||||
user.put("id", userDto.getId());
|
||||
|
@ -3,12 +3,22 @@
|
||||
/**
|
||||
* Document view controller.
|
||||
*/
|
||||
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload) {
|
||||
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) {
|
||||
// Load data from server
|
||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
||||
$scope.document = data;
|
||||
});
|
||||
|
||||
// Watch for ACLs change and group them for easy displaying
|
||||
$scope.$watch('document.acls', function(acls) {
|
||||
$scope.acls = _.groupBy(acls, function(acl) {
|
||||
return acl.id;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize add ACL
|
||||
$scope.acl = { perm: 'READ' };
|
||||
|
||||
/**
|
||||
* Configuration for file sorting.
|
||||
*/
|
||||
@ -17,7 +27,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
||||
forcePlaceholderSize: true,
|
||||
tolerance: 'pointer',
|
||||
handle: '.handle',
|
||||
stop: function (e, ui) {
|
||||
stop: function () {
|
||||
// Send new positions to server
|
||||
$scope.$apply(function () {
|
||||
Restangular.one('file').post('reorder', {
|
||||
@ -103,15 +113,11 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
||||
Restangular.one('share').put({
|
||||
name: name,
|
||||
id: $stateParams.id
|
||||
}).then(function (data) {
|
||||
var share = {
|
||||
name: name,
|
||||
id: data.id
|
||||
};
|
||||
|
||||
// Display the new share and add it to the local shares
|
||||
$scope.showShare(share);
|
||||
$scope.document.shares.push(share);
|
||||
}).then(function (acl) {
|
||||
// Display the new share ACL and add it to the local ACLs
|
||||
$scope.showShare(acl);
|
||||
$scope.document.acls.push(acl);
|
||||
$scope.document.acls = angular.copy($scope.document.acls);
|
||||
})
|
||||
});
|
||||
};
|
||||
@ -135,7 +141,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
||||
if (result == 'unshare') {
|
||||
// Unshare this document and update the local shares
|
||||
Restangular.one('share', share.id).remove().then(function () {
|
||||
$scope.document.shares = _.reject($scope.document.shares, function(s) {
|
||||
$scope.document.acls = _.reject($scope.document.acls, function(s) {
|
||||
return share.id == s.id;
|
||||
});
|
||||
});
|
||||
@ -148,6 +154,10 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
||||
* @param files
|
||||
*/
|
||||
$scope.fileDropped = function(files) {
|
||||
if (!$scope.document.writable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (files && files.length) {
|
||||
// Adding files to the UI
|
||||
var newfiles = [];
|
||||
@ -197,4 +207,42 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
|
||||
newfile.id = data.id;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete an ACL.
|
||||
* @param acl
|
||||
*/
|
||||
$scope.deleteAcl = function(acl) {
|
||||
Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
|
||||
$scope.document.acls = _.reject($scope.document.acls, function(s) {
|
||||
return angular.equals(acl, s);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an ACL.
|
||||
*/
|
||||
$scope.addAcl = function() {
|
||||
$scope.acl.source = $stateParams.id;
|
||||
Restangular.one('acl').put($scope.acl).then(function(acl) {
|
||||
$scope.acl = { perm: 'READ' };
|
||||
if (_.isUndefined(acl.id)) {
|
||||
return;
|
||||
}
|
||||
$scope.document.acls.push(acl);
|
||||
$scope.document.acls = angular.copy($scope.document.acls);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getTargetAclTypeahead = function($viewValue) {
|
||||
var deferred = $q.defer();
|
||||
Restangular.one('acl/target/search')
|
||||
.get({
|
||||
search: $viewValue
|
||||
}).then(function(data) {
|
||||
deferred.resolve(_.pluck(data.users, 'username'), true);
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
});
|
@ -6,7 +6,9 @@
|
||||
angular.module('docs').controller('Login', function($scope, $rootScope, $state, $dialog, User) {
|
||||
$scope.login = function() {
|
||||
User.login($scope.user).then(function() {
|
||||
$rootScope.userInfo = User.userInfo(true);
|
||||
User.userInfo(true).then(function(data) {
|
||||
$rootScope.userInfo = data;
|
||||
});
|
||||
$state.transitionTo('document.default');
|
||||
}, function() {
|
||||
var title = 'Login failed';
|
||||
|
@ -104,6 +104,9 @@
|
||||
<span class="glyphicon glyphicon-warning-sign"></span> {{ errorNumber }} new error{{ errorNumber > 1 ? 's' : '' }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/settings/account" title="Logged in as {{ userInfo.username }}">{{ userInfo.username }}</a>
|
||||
</li>
|
||||
<li ng-class="{active: $uiRoute}" ui-route="/settings.*">
|
||||
<a href="#/settings/account">
|
||||
<span class="glyphicon glyphicon-cog"></span> Settings
|
||||
|
@ -1,7 +1,7 @@
|
||||
<img src="img/loader.gif" ng-show="!document" />
|
||||
|
||||
<div ng-show="document">
|
||||
<div class="text-right">
|
||||
<div class="text-right" ng-show="document.writable">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger" ng-click="deleteDocument(document)"><span class="glyphicon glyphicon-trash"></span> Delete</button>
|
||||
<a href="#/document/edit/{{ document.id }}" class="btn btn-primary"><span class="glyphicon glyphicon-pencil"></span> Edit</a>
|
||||
@ -10,17 +10,17 @@
|
||||
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
|
||||
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }} by {{ document.creator }}</small>
|
||||
<img ng-if="document" ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" />
|
||||
<a ng-href="../api/file/zip?id={{ document.id }}" class="btn btn-default" title="Download all files">
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
</h1>
|
||||
<p>
|
||||
<p ng-show="document.writable">
|
||||
<button class="btn btn-sm btn-info" ng-click="share()">
|
||||
<span class="glyphicon glyphicon-share"></span> Share
|
||||
</button>
|
||||
<button class="btn btn-default btn-sm" ng-repeat="share in document.shares" ng-click="showShare(share)">
|
||||
<button class="btn btn-default btn-sm" ng-repeat="share in document.acls | filter: { 'type': 'SHARE' }" ng-click="showShare(share)">
|
||||
<span class="glyphicon glyphicon-ok"></span> {{ share.name ? share.name : 'shared' }}
|
||||
</button>
|
||||
</p>
|
||||
@ -30,44 +30,112 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p ng-bind-html="document.description | newline"></p>
|
||||
|
||||
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
|
||||
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail" ng-if="file.id">
|
||||
<a ng-click="openFile(file)">
|
||||
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
||||
</a>
|
||||
<div class="caption">
|
||||
<div class="pull-left">
|
||||
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
|
||||
<tabset>
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
<span class="glyphicon glyphicon-file"></span> Content
|
||||
</tab-heading>
|
||||
|
||||
<p ng-bind-html="document.description | newline"></p>
|
||||
|
||||
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
|
||||
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail" ng-if="file.id">
|
||||
<a ng-click="openFile(file)">
|
||||
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
||||
</a>
|
||||
<div class="caption" ng-show="document.writable">
|
||||
<div class="pull-left">
|
||||
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
|
||||
<div class="thumbnail" ng-if="!file.id">
|
||||
<p class="text-center lead">
|
||||
{{ file.status }}
|
||||
</p>
|
||||
<div class="caption">
|
||||
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail" ng-if="!file.id">
|
||||
<p class="text-center lead">
|
||||
{{ file.status }}
|
||||
<p class="text-center well-lg" ng-if="files.length == 0">
|
||||
<span class="glyphicon glyphicon-move"></span>
|
||||
Drag & drop files here to upload
|
||||
</p>
|
||||
<div class="caption">
|
||||
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
<span class="glyphicon glyphicon-user"></span> Permissions
|
||||
</tab-heading>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width: 40%">For</th>
|
||||
<th style="width: 40%">Permission</th>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="(id, acl) in acls">
|
||||
<td><em>{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }}</em> {{ acl[0].name }}</td>
|
||||
<td>
|
||||
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
|
||||
{{ a.perm }}
|
||||
<span ng-show="document.creator != a.name && a.type == 'USER' && document.writable"
|
||||
class="glyphicon glyphicon-remove pointer"
|
||||
ng-click="deleteAcl(a)"></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div ng-show="document.writable">
|
||||
<h4>Add a permission</h4>
|
||||
|
||||
<form name="aclForm" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputTarget">User</label>
|
||||
<div class="col-sm-3">
|
||||
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
|
||||
placeholder="Type a username" name="username" ng-model="acl.username" autocomplete="off"
|
||||
typeahead="username for username in getTargetAclTypeahead($viewValue) | filter: $viewValue"
|
||||
typeahead-wait-ms="200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" ng-model="acl.perm" id="inputPermission">
|
||||
<option value="READ">Read</option>
|
||||
<option value="WRITE">Write</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="!aclForm.$valid" ng-click="addAcl()">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
|
||||
<p class="text-center well-lg" ng-if="files.length == 0">
|
||||
<span class="glyphicon glyphicon-move"></span>
|
||||
Drag & drop files here to upload
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ui-view="file"></div>
|
||||
</div>
|
@ -179,4 +179,8 @@ input[readonly].share-link {
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
margin-top: 20px;
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=7
|
||||
db.version=8
|
@ -0,0 +1,177 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
import com.sun.jersey.core.util.MultivaluedMapImpl;
|
||||
|
||||
/**
|
||||
* Test the ACL resource.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestAclResource extends BaseJerseyTest {
|
||||
/**
|
||||
* Test the ACL resource.
|
||||
*
|
||||
* @throws JSONException
|
||||
*/
|
||||
@Test
|
||||
public void testAclResource() throws JSONException {
|
||||
// Login acl1
|
||||
clientUtil.createUser("acl1");
|
||||
String acl1Token = clientUtil.login("acl1");
|
||||
|
||||
// Login acl2
|
||||
clientUtil.createUser("acl2");
|
||||
String acl2Token = clientUtil.login("acl2");
|
||||
|
||||
// Create a document
|
||||
WebResource documentResource = resource().path("/document");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
|
||||
postParams.add("title", "My super title document 1");
|
||||
postParams.add("language", "eng");
|
||||
postParams.add("create_date", new Date().getTime());
|
||||
ClientResponse response = documentResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONObject json = response.getEntity(JSONObject.class);
|
||||
String document1Id = json.optString("id");
|
||||
|
||||
// Get the document as acl1
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(document1Id, json.getString("id"));
|
||||
JSONArray acls = json.getJSONArray("acls");
|
||||
Assert.assertEquals(2, acls.length());
|
||||
|
||||
// Get the document as acl2
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl2Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Add an ACL READ for acl2 with acl1
|
||||
WebResource aclResource = resource().path("/acl");
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("source", document1Id);
|
||||
postParams.add("perm", "READ");
|
||||
postParams.add("username", "acl2");
|
||||
response = aclResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
String acl2Id = json.getString("id");
|
||||
|
||||
// Add an ACL WRITE for acl2 with acl1
|
||||
aclResource = resource().path("/acl");
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("source", document1Id);
|
||||
postParams.add("perm", "WRITE");
|
||||
postParams.add("username", "acl2");
|
||||
response = aclResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Add an ACL WRITE for acl2 with acl1 (again)
|
||||
aclResource = resource().path("/acl");
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("source", document1Id);
|
||||
postParams.add("perm", "WRITE");
|
||||
postParams.add("username", "acl2");
|
||||
response = aclResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Get the document as acl1
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(document1Id, json.getString("id"));
|
||||
acls = json.getJSONArray("acls");
|
||||
Assert.assertEquals(4, acls.length());
|
||||
|
||||
// Get the document as acl2
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl2Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(document1Id, json.getString("id"));
|
||||
acls = json.getJSONArray("acls");
|
||||
Assert.assertEquals(4, acls.length());
|
||||
|
||||
// Delete the ACL WRITE for acl2 with acl2
|
||||
aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl2Id);
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl2Token));
|
||||
response = aclResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Delete the ACL READ for acl2 with acl2
|
||||
aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id);
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl2Token));
|
||||
response = aclResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Delete the ACL READ for acl2 with acl1
|
||||
aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id);
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = aclResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Get the document as acl1
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(document1Id, json.getString("id"));
|
||||
acls = json.getJSONArray("acls");
|
||||
Assert.assertEquals(2, acls.length());
|
||||
String acl1Id = acls.getJSONObject(0).getString("id");
|
||||
|
||||
// Get the document as acl2
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(acl2Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Delete the ACL READ for acl1 with acl1
|
||||
aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl1Id);
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = aclResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Delete the ACL WRITE for acl1 with acl1
|
||||
aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl1Id);
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = aclResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Search target list
|
||||
aclResource = resource().path("/acl/target/search");
|
||||
aclResource.addFilter(new CookieAuthenticationFilter(acl1Token));
|
||||
response = aclResource.queryParam("search", "acl").get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONArray users = json.getJSONArray("users");
|
||||
Assert.assertEquals(2, users.length());
|
||||
}
|
||||
}
|
@ -43,6 +43,10 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
clientUtil.createUser("document1");
|
||||
String document1Token = clientUtil.login("document1");
|
||||
|
||||
// Login document3
|
||||
clientUtil.createUser("document3");
|
||||
String document3Token = clientUtil.login("document3");
|
||||
|
||||
// Create a tag
|
||||
WebResource tagResource = resource().path("/tag");
|
||||
tagResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
@ -115,6 +119,61 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("SuperTag", tags.getJSONObject(0).getString("name"));
|
||||
Assert.assertEquals("#ffff00", tags.getJSONObject(0).getString("color"));
|
||||
|
||||
// List all documents from document3
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document3Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("sort_column", 3);
|
||||
getParams.putSingle("asc", false);
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
|
||||
// Create a document with document3
|
||||
documentResource = resource().path("/document");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document3Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("title", "My super title document 1");
|
||||
postParams.add("description", "My super description for document 1");
|
||||
postParams.add("language", "eng");
|
||||
create1Date = new Date().getTime();
|
||||
postParams.add("create_date", create1Date);
|
||||
response = documentResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
String document3Id = json.optString("id");
|
||||
Assert.assertNotNull(document3Id);
|
||||
|
||||
// Add a file
|
||||
fileResource = resource().path("/file");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
form = new FormDataMultiPart();
|
||||
file = this.getClass().getResourceAsStream("/file/Einstein-Roosevelt-letter.png");
|
||||
fdp = new FormDataBodyPart("file",
|
||||
new BufferedInputStream(file),
|
||||
MediaType.APPLICATION_OCTET_STREAM_TYPE);
|
||||
form.bodyPart(fdp);
|
||||
form.field("id", document1Id);
|
||||
response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
String file3Id = json.getString("id");
|
||||
Assert.assertNotNull(file3Id);
|
||||
|
||||
// List all documents from document3
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document3Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("sort_column", 3);
|
||||
getParams.putSingle("asc", false);
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
|
||||
// Search documents
|
||||
Assert.assertEquals(1, searchDocuments("full:uranium full:einstein", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("full:title", document1Token));
|
||||
|
@ -24,7 +24,6 @@ public class TestLocaleResource extends BaseJerseyTest {
|
||||
public void testLocaleResource() throws JSONException {
|
||||
WebResource localeResource = resource().path("/locale");
|
||||
ClientResponse response = localeResource.get(ClientResponse.class);
|
||||
response = localeResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONObject json = response.getEntity(JSONObject.class);
|
||||
JSONArray locale = json.getJSONArray("locales");
|
||||
|
@ -83,9 +83,7 @@ public class TestShareResource extends BaseJerseyTest {
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(document1Id, json.getString("id"));
|
||||
Assert.assertEquals(1, json.getJSONArray("shares").length());
|
||||
Assert.assertEquals(share1Id, json.getJSONArray("shares").getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals("4 All", json.getJSONArray("shares").getJSONObject(0).getString("name"));
|
||||
Assert.assertEquals(3, json.getJSONArray("acls").length()); // 2 for the creator, 1 for the share
|
||||
|
||||
// Get all files from this document anonymously
|
||||
fileResource = resource().path("/file/list");
|
||||
|
Loading…
Reference in New Issue
Block a user