mirror of
https://github.com/sismics/docs.git
synced 2024-11-25 23:27:57 +01:00
commit
05bfaa0035
@ -3,7 +3,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.1.0'
|
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
@ -14,7 +14,7 @@ repositories {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 23
|
||||||
buildToolsVersion '23.0.3'
|
buildToolsVersion '24'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
@ -50,13 +50,13 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile fileTree(dir: 'libs', include: '*.jar')
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
compile 'com.android.support:appcompat-v7:23.4.0'
|
||||||
compile 'com.android.support:recyclerview-v7:23.3.0'
|
compile 'com.android.support:recyclerview-v7:23.4.0'
|
||||||
compile 'com.android.support:design:23.3.0'
|
compile 'com.android.support:design:23.4.0'
|
||||||
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||||
compile 'org.greenrobot:eventbus:3.0.0'
|
compile 'org.greenrobot:eventbus:3.0.0'
|
||||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||||
compile 'com.squareup.okhttp3:okhttp:3.1.1'
|
compile 'com.squareup.okhttp3:okhttp:3.3.1'
|
||||||
compile "com.squareup.okhttp3:okhttp-urlconnection:3.1.1"
|
compile "com.squareup.okhttp3:okhttp-urlconnection:3.3.1"
|
||||||
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
|
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ public class DocumentEditActivity extends AppCompatActivity {
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JSONArray tagArray = tags.optJSONArray("stats");
|
JSONArray tagArray = tags.optJSONArray("tags");
|
||||||
|
|
||||||
List<JSONObject> tagList = new ArrayList<>();
|
List<JSONObject> tagList = new ArrayList<>();
|
||||||
for (int i = 0; i < tagArray.length(); i++) {
|
for (int i = 0; i < tagArray.length(); i++) {
|
||||||
|
@ -16,6 +16,7 @@ import android.support.v7.app.AppCompatActivity;
|
|||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -51,7 +52,7 @@ import com.sismics.docs.resource.FileResource;
|
|||||||
import com.sismics.docs.service.FileUploadService;
|
import com.sismics.docs.service.FileUploadService;
|
||||||
import com.sismics.docs.util.NetworkUtil;
|
import com.sismics.docs.util.NetworkUtil;
|
||||||
import com.sismics.docs.util.PreferenceUtil;
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
import com.sismics.docs.util.TagUtil;
|
import com.sismics.docs.util.SpannableUtil;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
@ -176,9 +177,11 @@ public class DocumentViewActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill the layout
|
// Fill the layout
|
||||||
|
// Create date
|
||||||
TextView createdDateTextView = (TextView) findViewById(R.id.createdDateTextView);
|
TextView createdDateTextView = (TextView) findViewById(R.id.createdDateTextView);
|
||||||
createdDateTextView.setText(date);
|
createdDateTextView.setText(date);
|
||||||
|
|
||||||
|
// Description
|
||||||
TextView descriptionTextView = (TextView) findViewById(R.id.descriptionTextView);
|
TextView descriptionTextView = (TextView) findViewById(R.id.descriptionTextView);
|
||||||
if (description.isEmpty() || document.isNull("description")) {
|
if (description.isEmpty() || document.isNull("description")) {
|
||||||
descriptionTextView.setVisibility(View.GONE);
|
descriptionTextView.setVisibility(View.GONE);
|
||||||
@ -187,17 +190,20 @@ public class DocumentViewActivity extends AppCompatActivity {
|
|||||||
descriptionTextView.setText(description);
|
descriptionTextView.setText(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
TextView tagTextView = (TextView) findViewById(R.id.tagTextView);
|
TextView tagTextView = (TextView) findViewById(R.id.tagTextView);
|
||||||
if (tags.length() == 0) {
|
if (tags.length() == 0) {
|
||||||
tagTextView.setVisibility(View.GONE);
|
tagTextView.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
tagTextView.setVisibility(View.VISIBLE);
|
tagTextView.setVisibility(View.VISIBLE);
|
||||||
tagTextView.setText(TagUtil.buildSpannable(tags));
|
tagTextView.setText(SpannableUtil.buildSpannableTags(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Language
|
||||||
ImageView languageImageView = (ImageView) findViewById(R.id.languageImageView);
|
ImageView languageImageView = (ImageView) findViewById(R.id.languageImageView);
|
||||||
languageImageView.setImageResource(getResources().getIdentifier(language, "drawable", getPackageName()));
|
languageImageView.setImageResource(getResources().getIdentifier(language, "drawable", getPackageName()));
|
||||||
|
|
||||||
|
// Shared status
|
||||||
ImageView sharedImageView = (ImageView) findViewById(R.id.sharedImageView);
|
ImageView sharedImageView = (ImageView) findViewById(R.id.sharedImageView);
|
||||||
sharedImageView.setVisibility(shared ? View.VISIBLE : View.GONE);
|
sharedImageView.setVisibility(shared ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
@ -642,10 +648,10 @@ public class DocumentViewActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Action only available if the document is writable
|
// Action only available if the document is writable
|
||||||
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||||
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||||
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||||
findViewById(R.id.actionDelete).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
findViewById(R.id.actionDelete).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
// ACLs
|
// ACLs
|
||||||
ListView aclListView = (ListView) findViewById(R.id.aclListView);
|
ListView aclListView = (ListView) findViewById(R.id.aclListView);
|
||||||
@ -679,10 +685,54 @@ public class DocumentViewActivity extends AppCompatActivity {
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Contributors
|
||||||
|
TextView contributorsTextView = (TextView) findViewById(R.id.contributorsTextView);
|
||||||
|
contributorsTextView.setText(SpannableUtil.buildSpannableContributors(document.optJSONArray("contributors")));
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
JSONArray relations = document.optJSONArray("relations");
|
||||||
|
if (relations.length() > 0) {
|
||||||
|
TextView relationsTextView = (TextView) findViewById(R.id.relationsTextView);
|
||||||
|
relationsTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
relationsTextView.setText(SpannableUtil.buildSpannableRelations(relations));
|
||||||
|
} else {
|
||||||
|
findViewById(R.id.relationsLayout).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional dublincore metadata
|
||||||
|
displayDublincoreMetadata(R.id.subjectTextView, R.id.subjectLayout, "subject");
|
||||||
|
displayDublincoreMetadata(R.id.identifierTextView, R.id.identifierLayout, "identifier");
|
||||||
|
displayDublincoreMetadata(R.id.publisherTextView, R.id.publisherLayout, "publisher");
|
||||||
|
displayDublincoreMetadata(R.id.formatTextView, R.id.formatLayout, "format");
|
||||||
|
displayDublincoreMetadata(R.id.sourceTextView, R.id.sourceLayout, "source");
|
||||||
|
displayDublincoreMetadata(R.id.typeTextView, R.id.typeLayout, "type");
|
||||||
|
displayDublincoreMetadata(R.id.coverageTextView, R.id.coverageLayout, "coverage");
|
||||||
|
displayDublincoreMetadata(R.id.rightsTextView, R.id.rightsLayout, "rights");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a dublincore metadata.
|
||||||
|
*
|
||||||
|
* @param textViewId TextView ID
|
||||||
|
* @param blockViewId View ID
|
||||||
|
* @param name Name
|
||||||
|
*/
|
||||||
|
private void displayDublincoreMetadata(int textViewId, int blockViewId, String name) {
|
||||||
|
if (document == null) return;
|
||||||
|
String value = document.optString(name);
|
||||||
|
if (document.isNull(name) || value.isEmpty()) {
|
||||||
|
findViewById(blockViewId).setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById(blockViewId).setVisibility(View.VISIBLE);
|
||||||
|
TextView textView = (TextView) findViewById(textViewId);
|
||||||
|
textView.setText(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
switch (view.getId()) {
|
switch (view.getId()) {
|
||||||
|
@ -9,7 +9,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.sismics.docs.R;
|
import com.sismics.docs.R;
|
||||||
import com.sismics.docs.util.TagUtil;
|
import com.sismics.docs.util.SpannableUtil;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -69,7 +69,7 @@ public class DocListAdapter extends RecyclerView.Adapter<DocListAdapter.ViewHold
|
|||||||
holder.titleTextView.setText(document.optString("title"));
|
holder.titleTextView.setText(document.optString("title"));
|
||||||
|
|
||||||
JSONArray tags = document.optJSONArray("tags");
|
JSONArray tags = document.optJSONArray("tags");
|
||||||
holder.subtitleTextView.setText(TagUtil.buildSpannable(tags));
|
holder.subtitleTextView.setText(SpannableUtil.buildSpannableTags(tags));
|
||||||
|
|
||||||
String date = DateFormat.getDateFormat(holder.dateTextView.getContext()).format(new Date(document.optLong("create_date")));
|
String date = DateFormat.getDateFormat(holder.dateTextView.getContext()).format(new Date(document.optLong("create_date")));
|
||||||
holder.dateTextView.setText(date);
|
holder.dateTextView.setText(date);
|
||||||
|
@ -73,7 +73,7 @@ public class SearchFragment extends DialogFragment {
|
|||||||
dialog.cancel();
|
dialog.cancel();
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
JSONArray tagArray = tags.optJSONArray("stats");
|
JSONArray tagArray = tags.optJSONArray("tags");
|
||||||
|
|
||||||
List<JSONObject> tagList = new ArrayList<>();
|
List<JSONObject> tagList = new ArrayList<>();
|
||||||
for (int i = 0; i < tagArray.length(); i++) {
|
for (int i = 0; i < tagArray.length(); i++) {
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-scrollable ListView.
|
||||||
|
* All items are visible from the start.
|
||||||
|
*
|
||||||
|
* @author http://stackoverflow.com/questions/18813296/non-scrollable-listview-inside-scrollview/24629341#24629341
|
||||||
|
*/
|
||||||
|
public class NonScrollListView extends ListView {
|
||||||
|
|
||||||
|
public NonScrollListView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
public NonScrollListView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
|
||||||
|
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
|
||||||
|
ViewGroup.LayoutParams params = getLayoutParams();
|
||||||
|
params.height = getMeasuredHeight();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for spannable.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SpannableUtil {
|
||||||
|
/**
|
||||||
|
* Create a colored spannable from tags.
|
||||||
|
*
|
||||||
|
* @param tags Tags
|
||||||
|
* @return Colored spannable
|
||||||
|
*/
|
||||||
|
public static Spannable buildSpannableTags(JSONArray tags) {
|
||||||
|
return buildSpannable(tags, "name", "color");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a spannable for contributors.
|
||||||
|
*
|
||||||
|
* @param contributors Contributors
|
||||||
|
* @return Spannable
|
||||||
|
*/
|
||||||
|
public static Spannable buildSpannableContributors(JSONArray contributors) {
|
||||||
|
return buildSpannable(contributors, "username", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a spannable for relations.
|
||||||
|
*
|
||||||
|
* @param relations Relations
|
||||||
|
* @return Spannable
|
||||||
|
*/
|
||||||
|
public static Spannable buildSpannableRelations(JSONArray relations) {
|
||||||
|
return buildSpannable(relations, "title", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a spannable from a JSONArray.
|
||||||
|
*
|
||||||
|
* @param array JSONArray
|
||||||
|
* @param valueName Name of the value part
|
||||||
|
* @param colorName Name of the color part (optional)
|
||||||
|
* @return Spannable
|
||||||
|
*/
|
||||||
|
private static Spannable buildSpannable(JSONArray array, String valueName, String colorName) {
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
final JSONObject tag = array.optJSONObject(i);
|
||||||
|
int start = builder.length();
|
||||||
|
builder.append(" ").append(tag.optString(valueName)).append(" ");
|
||||||
|
builder.setSpan(new ForegroundColorSpan(Color.WHITE), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
builder.setSpan(new BackgroundColorSpan(Color.parseColor(tag.optString(colorName, "#5bc0de"))), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
/*
|
||||||
|
TODO : Make tags, relations and contributors clickable
|
||||||
|
builder.setSpan(new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint ds) {
|
||||||
|
super.updateDrawState(ds);
|
||||||
|
ds.setColor(Color.WHITE);
|
||||||
|
ds.setUnderlineText(false);
|
||||||
|
}
|
||||||
|
}, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);*/
|
||||||
|
builder.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
package com.sismics.docs.util;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.style.BackgroundColorSpan;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for tags.
|
|
||||||
*
|
|
||||||
* @author bgamard.
|
|
||||||
*/
|
|
||||||
public class TagUtil {
|
|
||||||
/**
|
|
||||||
* Create a colored spannable from tags.
|
|
||||||
*
|
|
||||||
* @param tags Tags
|
|
||||||
* @return Colored spannable
|
|
||||||
*/
|
|
||||||
public static Spannable buildSpannable(JSONArray tags) {
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < tags.length(); i++) {
|
|
||||||
JSONObject tag = tags.optJSONObject(i);
|
|
||||||
int start = builder.length();
|
|
||||||
builder.append(" ").append(tag.optString("name")).append(" ");
|
|
||||||
builder.setSpan(new ForegroundColorSpan(Color.WHITE), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
builder.setSpan(new BackgroundColorSpan(Color.parseColor(tag.optString("color"))), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
builder.append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
@ -142,15 +142,19 @@
|
|||||||
|
|
||||||
<!-- Right drawer -->
|
<!-- Right drawer -->
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:id="@+id/right_drawer"
|
android:id="@+id/right_drawer"
|
||||||
android:layout_width="300dp"
|
android:layout_width="300dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="end"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:background="#fff"
|
android:background="#fff"
|
||||||
android:elevation="5dp">
|
android:elevation="5dp"
|
||||||
|
android:layout_gravity="end">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
|
|
||||||
@ -166,28 +170,6 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
style="?android:buttonBarStyle">
|
style="?android:buttonBarStyle">
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/actionEditDocument"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:drawableTop="@drawable/ic_create_grey600_24dp"
|
|
||||||
style="?android:buttonBarButtonStyle"
|
|
||||||
android:text="@string/edit_document"
|
|
||||||
android:textColor="#ff5a595b"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:layout_margin="8dp"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/actionUploadFile"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:drawableTop="@drawable/ic_file_upload_grey600_24dp"
|
|
||||||
style="?android:buttonBarButtonStyle"
|
|
||||||
android:text="@string/upload_file"
|
|
||||||
android:textColor="#ff5a595b"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:layout_margin="8dp"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/actionDownload"
|
android:id="@+id/actionDownload"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -199,6 +181,28 @@
|
|||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:layout_margin="8dp"/>
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/actionExportPdf"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableTop="@drawable/ic_description_grey600_24dp"
|
||||||
|
style="?android:buttonBarButtonStyle"
|
||||||
|
android:text="@string/export_pdf"
|
||||||
|
android:textColor="#ff5a595b"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/actionAuditLog"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableTop="@drawable/ic_assignment_grey600_24dp"
|
||||||
|
style="?android:buttonBarButtonStyle"
|
||||||
|
android:text="@string/activity"
|
||||||
|
android:textColor="#ff5a595b"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -208,12 +212,23 @@
|
|||||||
style="?android:buttonBarStyle">
|
style="?android:buttonBarStyle">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/actionExportPdf"
|
android:id="@+id/actionEditDocument"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:drawableTop="@drawable/ic_description_grey600_24dp"
|
android:drawableTop="@drawable/ic_create_grey600_24dp"
|
||||||
style="?android:buttonBarButtonStyle"
|
style="?android:buttonBarButtonStyle"
|
||||||
android:text="@string/export_pdf"
|
android:text="@string/edit_document"
|
||||||
|
android:textColor="#ff5a595b"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_margin="0dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/actionUploadFile"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableTop="@drawable/ic_file_upload_grey600_24dp"
|
||||||
|
style="?android:buttonBarButtonStyle"
|
||||||
|
android:text="@string/upload_file"
|
||||||
android:textColor="#ff5a595b"
|
android:textColor="#ff5a595b"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:layout_margin="0dp"/>
|
android:layout_margin="0dp"/>
|
||||||
@ -229,17 +244,6 @@
|
|||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:layout_margin="0dp"/>
|
android:layout_margin="0dp"/>
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/actionAuditLog"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:drawableTop="@drawable/ic_assignment_grey600_24dp"
|
|
||||||
style="?android:buttonBarButtonStyle"
|
|
||||||
android:text="@string/activity"
|
|
||||||
android:textColor="#ff5a595b"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:layout_margin="0dp"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/actionDelete"
|
android:id="@+id/actionDelete"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -285,6 +289,8 @@
|
|||||||
android:id="@+id/createdDateTextView"
|
android:id="@+id/createdDateTextView"
|
||||||
android:layout_toRightOf="@id/createdDateLabel"
|
android:layout_toRightOf="@id/createdDateLabel"
|
||||||
android:layout_toEndOf="@id/createdDateLabel"
|
android:layout_toEndOf="@id/createdDateLabel"
|
||||||
|
android:layout_toLeftOf="@id/sharedImageView"
|
||||||
|
android:layout_toStartOf="@id/sharedImageView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
@ -332,6 +338,7 @@
|
|||||||
android:fontFamily="sans-serif-light"/>
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:contentDescription="@string/shared"
|
||||||
android:id="@+id/sharedImageView"
|
android:id="@+id/sharedImageView"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
@ -343,6 +350,7 @@
|
|||||||
android:layout_toStartOf="@+id/languageImageView"/>
|
android:layout_toStartOf="@+id/languageImageView"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:contentDescription="@string/language"
|
||||||
android:id="@+id/languageImageView"
|
android:id="@+id/languageImageView"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
@ -352,6 +360,278 @@
|
|||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<!-- Additional dublincore metadata -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<!-- Subject -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/subjectLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/subject"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subjectTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Identifier -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/identifierLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/identifier"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/identifierTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Publisher -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/publisherLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/publisher"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/publisherTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Format -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/formatLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/format"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/formatTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Source -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/sourceLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/source"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sourceTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Type -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/typeLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/type"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/typeTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Coverage -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/coverageLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/coverage"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/coverageTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Rights -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/rightsLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/rights"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rightsTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Contributors -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/contributors"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contributorsTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Relations -->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/relationsLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="2dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_weight="0.33"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:text="@string/relations"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/relationsTextView"
|
||||||
|
android:layout_weight="0.67"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="sans-serif-light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
@ -369,14 +649,16 @@
|
|||||||
android:text="@string/who_can_access"
|
android:text="@string/who_can_access"
|
||||||
android:layout_margin="12dp"/>
|
android:layout_margin="12dp"/>
|
||||||
|
|
||||||
<ListView
|
<com.sismics.docs.ui.view.NonScrollListView
|
||||||
android:id="@+id/aclListView"
|
android:id="@+id/aclListView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:choiceMode="singleChoice"
|
android:choiceMode="singleChoice"
|
||||||
android:divider="@android:color/transparent"
|
android:divider="@android:color/transparent"
|
||||||
android:dividerHeight="0dp"/>
|
android:dividerHeight="0dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
</android.support.v4.widget.DrawerLayout>
|
</android.support.v4.widget.DrawerLayout>
|
@ -132,5 +132,17 @@
|
|||||||
<string name="storage_quota">Storage quota</string>
|
<string name="storage_quota">Storage quota</string>
|
||||||
<string name="storage_display">%1$d/%2$d MB</string>
|
<string name="storage_display">%1$d/%2$d MB</string>
|
||||||
<string name="validation_code">Validation code</string>
|
<string name="validation_code">Validation code</string>
|
||||||
|
<string name="shared">Shared</string>
|
||||||
|
<string name="language">Language</string>
|
||||||
|
<string name="coverage">Coverage</string>
|
||||||
|
<string name="type">Type</string>
|
||||||
|
<string name="source">Source</string>
|
||||||
|
<string name="format">Format</string>
|
||||||
|
<string name="publisher">Publisher</string>
|
||||||
|
<string name="identifier">Identifier</string>
|
||||||
|
<string name="subject">Subject</string>
|
||||||
|
<string name="rights">Rights</string>
|
||||||
|
<string name="contributors">Contributors</string>
|
||||||
|
<string name="relations">Relations</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
|
||||||
|
org.gradle.jvmargs=-Xmx3072m
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
@ -13,5 +13,10 @@ public enum ConfigType {
|
|||||||
/**
|
/**
|
||||||
* Theme configuration.
|
* Theme configuration.
|
||||||
*/
|
*/
|
||||||
THEME
|
THEME,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guest login.
|
||||||
|
*/
|
||||||
|
GUEST_LOGIN
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ public class Constants {
|
|||||||
*/
|
*/
|
||||||
public static final String LUCENE_DIRECTORY_STORAGE_FILE = "FILE";
|
public static final String LUCENE_DIRECTORY_STORAGE_FILE = "FILE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guest user ID.
|
||||||
|
*/
|
||||||
|
public static final String GUEST_USER_ID = "guest";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default generic user role.
|
* Default generic user role.
|
||||||
*/
|
*/
|
||||||
|
@ -1 +1 @@
|
|||||||
db.version=9
|
db.version=10
|
@ -0,0 +1,3 @@
|
|||||||
|
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('GUEST_LOGIN', 'false');
|
||||||
|
insert into T_USER(USE_ID_C, USE_IDROLE_C, USE_USERNAME_C, USE_PASSWORD_C, USE_EMAIL_C, USE_CREATEDATE_D, USE_PRIVATEKEY_C) values('guest', 'user', 'guest', '', 'guest@localhost', NOW(), 'GuestPk');
|
||||||
|
update T_CONFIG set CFG_VALUE_C = '10' where CFG_ID_C = 'DB_VERSION';
|
@ -59,4 +59,9 @@ public class AnonymousPrincipal implements IPrincipal {
|
|||||||
public Set<String> getGroupIdSet() {
|
public Set<String> getGroupIdSet() {
|
||||||
return Sets.newHashSet();
|
return Sets.newHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGuest() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,13 @@ public interface IPrincipal extends Principal {
|
|||||||
*/
|
*/
|
||||||
boolean isAnonymous();
|
boolean isAnonymous();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the principal is a guest.
|
||||||
|
*
|
||||||
|
* @return True if the principal is a guest
|
||||||
|
*/
|
||||||
|
boolean isGuest();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ID of the connected user, or null if the user is anonymous
|
* Returns the ID of the connected user, or null if the user is anonymous
|
||||||
*
|
*
|
||||||
|
@ -2,6 +2,7 @@ package com.sismics.security;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.sismics.docs.core.constant.Constants;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,4 +109,9 @@ public class UserPrincipal implements IPrincipal {
|
|||||||
public void setGroupIdSet(Set<String> groupIdSet) {
|
public void setGroupIdSet(Set<String> groupIdSet) {
|
||||||
this.groupIdSet = groupIdSet;
|
this.groupIdSet = groupIdSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGuest() {
|
||||||
|
return Constants.GUEST_USER_ID.equals(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.sismics.util.filter;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||||
|
import com.sismics.docs.core.model.jpa.User;
|
||||||
|
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A header-based security filter that authenticates an user using the "X-Authenticated-User" request header as the user ID.
|
||||||
|
* This filter is intended to be used in conjunction with an external authenticating proxy.
|
||||||
|
*
|
||||||
|
* @author pacien
|
||||||
|
*/
|
||||||
|
public class HeaderBasedSecurityFilter extends SecurityFilter {
|
||||||
|
/**
|
||||||
|
* Authentication header.
|
||||||
|
*/
|
||||||
|
public static final String AUTHENTICATED_USER_HEADER = "X-Authenticated-User";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this authentication method is enabled.
|
||||||
|
*/
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
enabled = Boolean.parseBoolean(filterConfig.getInitParameter("enabled"))
|
||||||
|
|| Boolean.parseBoolean(System.getProperty("docs.header_authentication"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected User authenticate(HttpServletRequest request) {
|
||||||
|
if (!enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = request.getHeader(AUTHENTICATED_USER_HEADER);
|
||||||
|
if (Strings.isNullOrEmpty(username)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new UserDao().getActiveByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package com.sismics.util.filter;
|
||||||
|
|
||||||
|
import com.sismics.docs.core.constant.Constants;
|
||||||
|
import com.sismics.docs.core.dao.jpa.GroupDao;
|
||||||
|
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
|
||||||
|
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
|
||||||
|
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
|
||||||
|
import com.sismics.docs.core.model.jpa.User;
|
||||||
|
import com.sismics.security.AnonymousPrincipal;
|
||||||
|
import com.sismics.security.UserPrincipal;
|
||||||
|
import jersey.repackaged.com.google.common.collect.Sets;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract security filter for user authentication, that injects corresponding users into the request.
|
||||||
|
* Successfully authenticated users are injected as UserPrincipal, or as AnonymousPrincipal otherwise.
|
||||||
|
* If an user has already been authenticated for the request, no further authentication attempt is made.
|
||||||
|
*
|
||||||
|
* @author pacien
|
||||||
|
* @author jtremeaux
|
||||||
|
*/
|
||||||
|
public abstract class SecurityFilter implements Filter {
|
||||||
|
/**
|
||||||
|
* Name of the attribute containing the principal.
|
||||||
|
*/
|
||||||
|
public static final String PRINCIPAL_ATTRIBUTE = "principal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger.
|
||||||
|
*/
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(SecurityFilter.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the supplied request has an UserPrincipal.
|
||||||
|
*
|
||||||
|
* @param request HTTP request
|
||||||
|
* @return True if the supplied request has an UserPrincipal
|
||||||
|
*/
|
||||||
|
private boolean hasIdentifiedUser(HttpServletRequest request) {
|
||||||
|
return request.getAttribute(PRINCIPAL_ATTRIBUTE) instanceof UserPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects the given user into the request, with the appropriate authentication state.
|
||||||
|
*
|
||||||
|
* @param request HTTP request
|
||||||
|
* @param user nullable User to inject
|
||||||
|
*/
|
||||||
|
private void injectUser(HttpServletRequest request, User user) {
|
||||||
|
// Check if the user is still valid
|
||||||
|
if (user != null && user.getDeleteDate() == null) {
|
||||||
|
injectAuthenticatedUser(request, user);
|
||||||
|
} else {
|
||||||
|
injectAnonymousUser(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject an authenticated user into the request attributes.
|
||||||
|
*
|
||||||
|
* @param request HTTP request
|
||||||
|
* @param user User to inject
|
||||||
|
*/
|
||||||
|
private void injectAuthenticatedUser(HttpServletRequest request, User user) {
|
||||||
|
UserPrincipal userPrincipal = new UserPrincipal(user.getId(), user.getUsername());
|
||||||
|
|
||||||
|
// Add groups
|
||||||
|
GroupDao groupDao = new GroupDao();
|
||||||
|
Set<String> groupRoleIdSet = new HashSet<>();
|
||||||
|
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
|
||||||
|
.setUserId(user.getId())
|
||||||
|
.setRecursive(true), null);
|
||||||
|
Set<String> groupIdSet = Sets.newHashSet();
|
||||||
|
for (GroupDto groupDto : groupDtoList) {
|
||||||
|
groupIdSet.add(groupDto.getId());
|
||||||
|
if (groupDto.getRoleId() != null) {
|
||||||
|
groupRoleIdSet.add(groupDto.getRoleId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userPrincipal.setGroupIdSet(groupIdSet);
|
||||||
|
|
||||||
|
// Add base functions
|
||||||
|
groupRoleIdSet.add(user.getRoleId());
|
||||||
|
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
|
||||||
|
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(groupRoleIdSet);
|
||||||
|
userPrincipal.setBaseFunctionSet(baseFunctionSet);
|
||||||
|
|
||||||
|
// Add email
|
||||||
|
userPrincipal.setEmail(user.getEmail());
|
||||||
|
|
||||||
|
request.setAttribute(PRINCIPAL_ATTRIBUTE, userPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject an anonymous user into the request attributes.
|
||||||
|
*
|
||||||
|
* @param request HTTP request
|
||||||
|
*/
|
||||||
|
private void injectAnonymousUser(HttpServletRequest request) {
|
||||||
|
AnonymousPrincipal anonymousPrincipal = new AnonymousPrincipal();
|
||||||
|
anonymousPrincipal.setDateTimeZone(DateTimeZone.forID(Constants.DEFAULT_TIMEZONE_ID));
|
||||||
|
|
||||||
|
request.setAttribute(PRINCIPAL_ATTRIBUTE, anonymousPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest req, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
HttpServletRequest request = (HttpServletRequest) req;
|
||||||
|
|
||||||
|
if (!hasIdentifiedUser(request)) {
|
||||||
|
User user = authenticate(request);
|
||||||
|
injectUser(request, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates an user from the given request parameters.
|
||||||
|
*
|
||||||
|
* @param request HTTP request
|
||||||
|
* @return nullable User
|
||||||
|
*/
|
||||||
|
protected abstract User authenticate(HttpServletRequest request);
|
||||||
|
|
||||||
|
}
|
@ -1,38 +1,14 @@
|
|||||||
package com.sismics.util.filter;
|
package com.sismics.util.filter;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.FilterConfig;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.joda.time.DateTimeZone;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.sismics.docs.core.constant.Constants;
|
|
||||||
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
|
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
|
||||||
import com.sismics.docs.core.dao.jpa.GroupDao;
|
|
||||||
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
|
|
||||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||||
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
|
|
||||||
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
|
|
||||||
import com.sismics.docs.core.model.jpa.AuthenticationToken;
|
import com.sismics.docs.core.model.jpa.AuthenticationToken;
|
||||||
import com.sismics.docs.core.model.jpa.User;
|
import com.sismics.docs.core.model.jpa.User;
|
||||||
import com.sismics.security.AnonymousPrincipal;
|
|
||||||
import com.sismics.security.UserPrincipal;
|
|
||||||
|
|
||||||
import jersey.repackaged.com.google.common.collect.Sets;
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This filter is used to authenticate the user having an active session via an authentication token stored in database.
|
* This filter is used to authenticate the user having an active session via an authentication token stored in database.
|
||||||
@ -42,22 +18,12 @@ import jersey.repackaged.com.google.common.collect.Sets;
|
|||||||
*
|
*
|
||||||
* @author jtremeaux
|
* @author jtremeaux
|
||||||
*/
|
*/
|
||||||
public class TokenBasedSecurityFilter implements Filter {
|
public class TokenBasedSecurityFilter extends SecurityFilter {
|
||||||
/**
|
|
||||||
* Logger.
|
|
||||||
*/
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(TokenBasedSecurityFilter.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the cookie used to store the authentication token.
|
* Name of the cookie used to store the authentication token.
|
||||||
*/
|
*/
|
||||||
public static final String COOKIE_NAME = "auth_token";
|
public static final String COOKIE_NAME = "auth_token";
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the attribute containing the principal.
|
|
||||||
*/
|
|
||||||
public static final String PRINCIPAL_ATTRIBUTE = "principal";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifetime of the authentication token in seconds, since login.
|
* Lifetime of the authentication token in seconds, since login.
|
||||||
*/
|
*/
|
||||||
@ -66,67 +32,39 @@ public class TokenBasedSecurityFilter implements Filter {
|
|||||||
/**
|
/**
|
||||||
* Lifetime of the authentication token in seconds, since last connection.
|
* Lifetime of the authentication token in seconds, since last connection.
|
||||||
*/
|
*/
|
||||||
public static final int TOKEN_SESSION_LIFETIME = 3600 * 24;
|
private static final int TOKEN_SESSION_LIFETIME = 3600 * 24;
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void init(FilterConfig filterConfig) throws ServletException {
|
* Extracts and returns an authentication token from a cookie list.
|
||||||
// NOP
|
*
|
||||||
}
|
* @param cookies Cookie list
|
||||||
|
* @return nullable auth token
|
||||||
@Override
|
*/
|
||||||
public void destroy() {
|
private String extractAuthToken(Cookie[] cookies) {
|
||||||
// NOP
|
if (cookies != null) {
|
||||||
}
|
for (Cookie cookie : cookies) {
|
||||||
|
if (COOKIE_NAME.equals(cookie.getName()) && !cookie.getValue().isEmpty()) {
|
||||||
@Override
|
return cookie.getValue();
|
||||||
public void doFilter(ServletRequest req, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
|
||||||
// Get the value of the client authentication token
|
|
||||||
HttpServletRequest request = (HttpServletRequest) req;
|
|
||||||
String authToken = null;
|
|
||||||
if (request.getCookies() != null) {
|
|
||||||
for (Cookie cookie : request.getCookies()) {
|
|
||||||
if (COOKIE_NAME.equals(cookie.getName())) {
|
|
||||||
authToken = cookie.getValue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the corresponding server token
|
return null;
|
||||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
|
||||||
AuthenticationToken authenticationToken = null;
|
|
||||||
if (authToken != null) {
|
|
||||||
authenticationToken = authenticationTokenDao.get(authToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authenticationToken == null) {
|
/**
|
||||||
injectAnonymousUser(request);
|
* Deletes an expired authentication token.
|
||||||
} else {
|
*
|
||||||
// Check if the token is still valid
|
* @param authTokenID auth token ID
|
||||||
if (isTokenExpired(authenticationToken)) {
|
*/
|
||||||
|
private void handleExpiredToken(AuthenticationTokenDao dao, String authTokenID) {
|
||||||
try {
|
try {
|
||||||
injectAnonymousUser(request);
|
dao.delete(authTokenID);
|
||||||
|
|
||||||
// Destroy the expired token
|
|
||||||
authenticationTokenDao.delete(authToken);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (log.isErrorEnabled()) {
|
if (LOG.isErrorEnabled())
|
||||||
log.error(MessageFormat.format("Error deleting authentication token {0} ", authToken), e);
|
LOG.error(MessageFormat.format("Error deleting authentication token {0} ", authTokenID), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Check if the user is still valid
|
|
||||||
UserDao userDao = new UserDao();
|
|
||||||
User user = userDao.getById(authenticationToken.getUserId());
|
|
||||||
if (user != null && user.getDeleteDate() == null) {
|
|
||||||
injectAuthenticatedUser(request, user);
|
|
||||||
} else {
|
|
||||||
injectAnonymousUser(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the token is expired.
|
* Returns true if the token is expired.
|
||||||
@ -146,51 +84,27 @@ public class TokenBasedSecurityFilter implements Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Inject an authenticated user into the request attributes.
|
protected User authenticate(HttpServletRequest request) {
|
||||||
*
|
// Get the value of the client authentication token
|
||||||
* @param request HTTP request
|
String authTokenId = extractAuthToken(request.getCookies());
|
||||||
* @param user User to inject
|
if (authTokenId == null) {
|
||||||
*/
|
return null;
|
||||||
private void injectAuthenticatedUser(HttpServletRequest request, User user) {
|
|
||||||
UserPrincipal userPrincipal = new UserPrincipal(user.getId(), user.getUsername());
|
|
||||||
|
|
||||||
// Add groups
|
|
||||||
GroupDao groupDao = new GroupDao();
|
|
||||||
Set<String> groupRoleIdSet = new HashSet<>();
|
|
||||||
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
|
|
||||||
.setUserId(user.getId())
|
|
||||||
.setRecursive(true), null);
|
|
||||||
Set<String> groupIdSet = Sets.newHashSet();
|
|
||||||
for (GroupDto groupDto : groupDtoList) {
|
|
||||||
groupIdSet.add(groupDto.getId());
|
|
||||||
if (groupDto.getRoleId() != null) {
|
|
||||||
groupRoleIdSet.add(groupDto.getRoleId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userPrincipal.setGroupIdSet(groupIdSet);
|
|
||||||
|
|
||||||
// Add base functions
|
|
||||||
groupRoleIdSet.add(user.getRoleId());
|
|
||||||
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
|
|
||||||
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(groupRoleIdSet);
|
|
||||||
userPrincipal.setBaseFunctionSet(baseFunctionSet);
|
|
||||||
|
|
||||||
// Add email
|
|
||||||
userPrincipal.setEmail(user.getEmail());
|
|
||||||
|
|
||||||
request.setAttribute(PRINCIPAL_ATTRIBUTE, userPrincipal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Get the corresponding server token
|
||||||
* Inject an anonymous user into the request attributes.
|
AuthenticationTokenDao authTokenDao = new AuthenticationTokenDao();
|
||||||
*
|
AuthenticationToken authToken = authTokenDao.get(authTokenId);
|
||||||
* @param request HTTP request
|
if (authToken == null) {
|
||||||
*/
|
return null;
|
||||||
private void injectAnonymousUser(HttpServletRequest request) {
|
}
|
||||||
AnonymousPrincipal anonymousPrincipal = new AnonymousPrincipal();
|
|
||||||
anonymousPrincipal.setDateTimeZone(DateTimeZone.forID(Constants.DEFAULT_TIMEZONE_ID));
|
|
||||||
|
|
||||||
request.setAttribute(PRINCIPAL_ATTRIBUTE, anonymousPrincipal);
|
if (isTokenExpired(authToken)) {
|
||||||
|
handleExpiredToken(authTokenDao, authTokenId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
authTokenDao.updateLastConnectionDate(authToken.getId());
|
||||||
|
return new UserDao().getById(authToken.getUserId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import java.net.URI;
|
|||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
|
||||||
|
import com.sismics.util.filter.HeaderBasedSecurityFilter;
|
||||||
import org.glassfish.grizzly.http.server.HttpServer;
|
import org.glassfish.grizzly.http.server.HttpServer;
|
||||||
import org.glassfish.grizzly.servlet.ServletRegistration;
|
import org.glassfish.grizzly.servlet.ServletRegistration;
|
||||||
import org.glassfish.grizzly.servlet.WebappContext;
|
import org.glassfish.grizzly.servlet.WebappContext;
|
||||||
@ -62,6 +63,7 @@ public abstract class BaseJerseyTest extends JerseyTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
System.setProperty("docs.header_authentication", "true");
|
||||||
|
|
||||||
clientUtil = new ClientUtil(target());
|
clientUtil = new ClientUtil(target());
|
||||||
|
|
||||||
@ -71,6 +73,8 @@ public abstract class BaseJerseyTest extends JerseyTest {
|
|||||||
.addMappingForUrlPatterns(null, "/*");
|
.addMappingForUrlPatterns(null, "/*");
|
||||||
context.addFilter("tokenBasedSecurityFilter", TokenBasedSecurityFilter.class)
|
context.addFilter("tokenBasedSecurityFilter", TokenBasedSecurityFilter.class)
|
||||||
.addMappingForUrlPatterns(null, "/*");
|
.addMappingForUrlPatterns(null, "/*");
|
||||||
|
context.addFilter("headerBasedSecurityFilter", HeaderBasedSecurityFilter.class)
|
||||||
|
.addMappingForUrlPatterns(null, "/*");
|
||||||
ServletRegistration reg = context.addServlet("jerseyServlet", ServletContainer.class);
|
ServletRegistration reg = context.addServlet("jerseyServlet", ServletContainer.class);
|
||||||
reg.setInitParameter("jersey.config.server.provider.packages", "com.sismics.docs.rest.resource");
|
reg.setInitParameter("jersey.config.server.provider.packages", "com.sismics.docs.rest.resource");
|
||||||
reg.setInitParameter("jersey.config.server.provider.classnames", "org.glassfish.jersey.media.multipart.MultiPartFeature");
|
reg.setInitParameter("jersey.config.server.provider.classnames", "org.glassfish.jersey.media.multipart.MultiPartFeature");
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=9
|
db.version=10
|
@ -14,15 +14,12 @@ import javax.json.JsonArrayBuilder;
|
|||||||
import javax.json.JsonObjectBuilder;
|
import javax.json.JsonObjectBuilder;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.Query;
|
import javax.persistence.Query;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import com.sismics.docs.core.constant.ConfigType;
|
||||||
import com.sismics.docs.core.constant.PermType;
|
import com.sismics.docs.core.constant.PermType;
|
||||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
import com.sismics.docs.core.dao.jpa.*;
|
||||||
import com.sismics.docs.core.dao.jpa.TagDao;
|
|
||||||
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
|
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
|
||||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
||||||
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||||
@ -33,8 +30,6 @@ import org.apache.log4j.Level;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
|
||||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
|
||||||
import com.sismics.docs.core.model.context.AppContext;
|
import com.sismics.docs.core.model.context.AppContext;
|
||||||
import com.sismics.docs.core.model.jpa.File;
|
import com.sismics.docs.core.model.jpa.File;
|
||||||
import com.sismics.docs.core.model.jpa.User;
|
import com.sismics.docs.core.model.jpa.User;
|
||||||
@ -70,33 +65,59 @@ public class AppResource extends BaseResource {
|
|||||||
* @apiGroup App
|
* @apiGroup App
|
||||||
* @apiSuccess {String} current_version API current version
|
* @apiSuccess {String} current_version API current version
|
||||||
* @apiSuccess {String} min_version API minimum version
|
* @apiSuccess {String} min_version API minimum version
|
||||||
|
* @apiSuccess {Boolean} guest_login True if guest login is enabled
|
||||||
* @apiSuccess {String} total_memory Allocated JVM memory (in bytes)
|
* @apiSuccess {String} total_memory Allocated JVM memory (in bytes)
|
||||||
* @apiSuccess {String} free_memory Free JVM memory (in bytes)
|
* @apiSuccess {String} free_memory Free JVM memory (in bytes)
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiPermission none
|
||||||
* @apiPermission user
|
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
public Response info() {
|
public Response info() {
|
||||||
if (!authenticate()) {
|
|
||||||
throw new ForbiddenClientException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
|
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
|
||||||
String currentVersion = configBundle.getString("api.current_version");
|
String currentVersion = configBundle.getString("api.current_version");
|
||||||
String minVersion = configBundle.getString("api.min_version");
|
String minVersion = configBundle.getString("api.min_version");
|
||||||
|
Boolean guestLogin = ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN);
|
||||||
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||||
.add("current_version", currentVersion.replace("-SNAPSHOT", ""))
|
.add("current_version", currentVersion.replace("-SNAPSHOT", ""))
|
||||||
.add("min_version", minVersion)
|
.add("min_version", minVersion)
|
||||||
|
.add("guest_login", guestLogin)
|
||||||
.add("total_memory", Runtime.getRuntime().totalMemory())
|
.add("total_memory", Runtime.getRuntime().totalMemory())
|
||||||
.add("free_memory", Runtime.getRuntime().freeMemory());
|
.add("free_memory", Runtime.getRuntime().freeMemory());
|
||||||
|
|
||||||
return Response.ok().entity(response.build()).build();
|
return Response.ok().entity(response.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable guest login.
|
||||||
|
*
|
||||||
|
* @api {post} /app/guest_login Enable/disable guest login
|
||||||
|
* @apiName PostAppGuestLogin
|
||||||
|
* @apiGroup App
|
||||||
|
* @apiParam {Boolean} enabled If true, enable guest login
|
||||||
|
* @apiError (client) ForbiddenError Access denied
|
||||||
|
* @apiPermission admin
|
||||||
|
* @apiVersion 1.5.0
|
||||||
|
*
|
||||||
|
* @param enabled If true, enable guest login
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("guest_login")
|
||||||
|
public Response guestLogin(@FormParam("enabled") Boolean enabled) {
|
||||||
|
if (!authenticate()) {
|
||||||
|
throw new ForbiddenClientException();
|
||||||
|
}
|
||||||
|
checkBaseFunction(BaseFunction.ADMIN);
|
||||||
|
|
||||||
|
ConfigDao configDao = new ConfigDao();
|
||||||
|
configDao.update(ConfigType.GUEST_LOGIN, enabled.toString());
|
||||||
|
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the application logs.
|
* Retrieve the application logs.
|
||||||
*
|
*
|
||||||
@ -325,7 +346,7 @@ public class AppResource extends BaseResource {
|
|||||||
/**
|
/**
|
||||||
* Recompute the quota for each user.
|
* Recompute the quota for each user.
|
||||||
*
|
*
|
||||||
* @api {post} /app/batch/recompute_quote Recompute user quotas
|
* @api {post} /app/batch/recompute_quota Recompute user quotas
|
||||||
* @apiName PostAppBatchRecomputeQuota
|
* @apiName PostAppBatchRecomputeQuota
|
||||||
* @apiGroup App
|
* @apiGroup App
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
@ -385,7 +406,7 @@ public class AppResource extends BaseResource {
|
|||||||
/**
|
/**
|
||||||
* Add base ACLs to tags.
|
* Add base ACLs to tags.
|
||||||
*
|
*
|
||||||
* @api {post} /app/batch/recompute_quote Add base ACL to tags
|
* @api {post} /app/batch/tag_acls Add base ACL to tags
|
||||||
* @apiDescription This resource must be used after migrating to 1.5.
|
* @apiDescription This resource must be used after migrating to 1.5.
|
||||||
* It will not do anything if base ACL are already present on tags.
|
* It will not do anything if base ACL are already present on tags.
|
||||||
* @apiName PostAppBatchTagAcls
|
* @apiName PostAppBatchTagAcls
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package com.sismics.docs.rest.resource;
|
package com.sismics.docs.rest.resource;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.sismics.docs.rest.constant.BaseFunction;
|
import com.sismics.docs.rest.constant.BaseFunction;
|
||||||
import com.sismics.rest.exception.ForbiddenClientException;
|
import com.sismics.rest.exception.ForbiddenClientException;
|
||||||
import com.sismics.security.IPrincipal;
|
import com.sismics.security.IPrincipal;
|
||||||
import com.sismics.security.UserPrincipal;
|
import com.sismics.security.UserPrincipal;
|
||||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
import com.sismics.util.filter.SecurityFilter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class of REST resources.
|
* Base class of REST resources.
|
||||||
@ -67,7 +66,7 @@ public abstract class BaseResource {
|
|||||||
* @return True if the user is authenticated and not anonymous
|
* @return True if the user is authenticated and not anonymous
|
||||||
*/
|
*/
|
||||||
protected boolean authenticate() {
|
protected boolean authenticate() {
|
||||||
Principal principal = (Principal) request.getAttribute(TokenBasedSecurityFilter.PRINCIPAL_ATTRIBUTE);
|
Principal principal = (Principal) request.getAttribute(SecurityFilter.PRINCIPAL_ATTRIBUTE);
|
||||||
if (principal != null && principal instanceof IPrincipal) {
|
if (principal != null && principal instanceof IPrincipal) {
|
||||||
this.principal = (IPrincipal) principal;
|
this.principal = (IPrincipal) principal;
|
||||||
return !this.principal.isAnonymous();
|
return !this.principal.isAnonymous();
|
||||||
|
@ -22,6 +22,8 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import javax.ws.rs.core.NewCookie;
|
import javax.ws.rs.core.NewCookie;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import com.sismics.docs.core.constant.ConfigType;
|
||||||
|
import com.sismics.docs.core.util.ConfigUtil;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
@ -150,7 +152,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiParam {String{8..50}} password Password
|
* @apiParam {String{8..50}} password Password
|
||||||
* @apiParam {String{1..100}} email E-mail
|
* @apiParam {String{1..100}} email E-mail
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiError (client) ForbiddenError Access denied or connected as guest
|
||||||
* @apiError (client) ValidationError Validation error
|
* @apiError (client) ValidationError Validation error
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
@ -163,7 +165,7 @@ public class UserResource extends BaseResource {
|
|||||||
public Response update(
|
public Response update(
|
||||||
@FormParam("password") String password,
|
@FormParam("password") String password,
|
||||||
@FormParam("email") String email) {
|
@FormParam("email") String email) {
|
||||||
if (!authenticate()) {
|
if (!authenticate() || principal.isGuest()) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +303,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiName PostUserLogin
|
* @apiName PostUserLogin
|
||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiParam {String} username Username
|
* @apiParam {String} username Username
|
||||||
* @apiParam {String} password Password
|
* @apiParam {String} password Password (optional for guest login)
|
||||||
* @apiParam {String} code TOTP validation code
|
* @apiParam {String} code TOTP validation code
|
||||||
* @apiParam {Boolean} remember If true, create a long lasted token
|
* @apiParam {Boolean} remember If true, create a long lasted token
|
||||||
* @apiSuccess {String} auth_token A cookie named auth_token containing the token ID
|
* @apiSuccess {String} auth_token A cookie named auth_token containing the token ID
|
||||||
@ -328,7 +330,16 @@ public class UserResource extends BaseResource {
|
|||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
UserDao userDao = new UserDao();
|
UserDao userDao = new UserDao();
|
||||||
User user = userDao.authenticate(username, password);
|
User user = null;
|
||||||
|
if (Constants.GUEST_USER_ID.equals(username)) {
|
||||||
|
if (ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN)) {
|
||||||
|
// Login as guest
|
||||||
|
user = userDao.getActiveByUsername(Constants.GUEST_USER_ID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Login as a normal user
|
||||||
|
user = userDao.authenticate(username, password);
|
||||||
|
}
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
@ -429,7 +440,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiName DeleteUser
|
* @apiName DeleteUser
|
||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) ForbiddenError Access denied or the admin user cannot be deleted
|
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
*
|
*
|
||||||
@ -442,8 +453,8 @@ public class UserResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the admin user is not deleted
|
// Ensure that the admin user is not deleted
|
||||||
if (hasBaseFunction(BaseFunction.ADMIN)) {
|
if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) {
|
||||||
throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
|
throw new ClientException("ForbiddenError", "This user cannot be deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find linked data
|
// Find linked data
|
||||||
@ -486,7 +497,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiName DeleteUserUsername
|
* @apiName DeleteUserUsername
|
||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) ForbiddenError Access denied or the admin user cannot be deleted
|
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
|
||||||
* @apiError (client) UserNotFound The user does not exist
|
* @apiError (client) UserNotFound The user does not exist
|
||||||
* @apiPermission admin
|
* @apiPermission admin
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
@ -502,6 +513,11 @@ public class UserResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
checkBaseFunction(BaseFunction.ADMIN);
|
checkBaseFunction(BaseFunction.ADMIN);
|
||||||
|
|
||||||
|
// Cannot delete the guest user
|
||||||
|
if (Constants.GUEST_USER_ID.equals(username)) {
|
||||||
|
throw new ClientException("ForbiddenError", "The guest user cannot be deleted");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the user exists
|
// Check if the user exists
|
||||||
UserDao userDao = new UserDao();
|
UserDao userDao = new UserDao();
|
||||||
User user = userDao.getActiveByUsername(username);
|
User user = userDao.getActiveByUsername(username);
|
||||||
@ -768,8 +784,10 @@ public class UserResource extends BaseResource {
|
|||||||
String authToken = getAuthToken();
|
String authToken = getAuthToken();
|
||||||
|
|
||||||
JsonArrayBuilder sessions = Json.createArrayBuilder();
|
JsonArrayBuilder sessions = Json.createArrayBuilder();
|
||||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
|
||||||
|
|
||||||
|
// The guest user cannot see other sessions
|
||||||
|
if (!principal.isGuest()) {
|
||||||
|
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
||||||
for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
|
for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
|
||||||
JsonObjectBuilder session = Json.createObjectBuilder()
|
JsonObjectBuilder session = Json.createObjectBuilder()
|
||||||
.add("create_date", authenticationToken.getCreationDate().getTime())
|
.add("create_date", authenticationToken.getCreationDate().getTime())
|
||||||
@ -781,6 +799,7 @@ public class UserResource extends BaseResource {
|
|||||||
session.add("current", authenticationToken.getId().equals(authToken));
|
session.add("current", authenticationToken.getId().equals(authToken));
|
||||||
sessions.add(session);
|
sessions.add(session);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||||
.add("sessions", sessions);
|
.add("sessions", sessions);
|
||||||
@ -795,7 +814,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiName DeleteUserSession
|
* @apiName DeleteUserSession
|
||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiError (client) ForbiddenError Access denied or connected as guest
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
*
|
*
|
||||||
@ -804,7 +823,7 @@ public class UserResource extends BaseResource {
|
|||||||
@DELETE
|
@DELETE
|
||||||
@Path("session")
|
@Path("session")
|
||||||
public Response deleteSession() {
|
public Response deleteSession() {
|
||||||
if (!authenticate()) {
|
if (!authenticate() || principal.isGuest()) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,7 +849,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiName PostUserEnableTotp
|
* @apiName PostUserEnableTotp
|
||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiSuccess {String} secret Secret TOTP seed to initiate the algorithm
|
* @apiSuccess {String} secret Secret TOTP seed to initiate the algorithm
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiError (client) ForbiddenError Access denied or connected as guest
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
*
|
*
|
||||||
@ -839,7 +858,7 @@ public class UserResource extends BaseResource {
|
|||||||
@POST
|
@POST
|
||||||
@Path("enable_totp")
|
@Path("enable_totp")
|
||||||
public Response enableTotp() {
|
public Response enableTotp() {
|
||||||
if (!authenticate()) {
|
if (!authenticate() || principal.isGuest()) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,7 +885,7 @@ public class UserResource extends BaseResource {
|
|||||||
* @apiGroup User
|
* @apiGroup User
|
||||||
* @apiParam {String{1..100}} password Password
|
* @apiParam {String{1..100}} password Password
|
||||||
* @apiSuccess {String} status Status OK
|
* @apiSuccess {String} status Status OK
|
||||||
* @apiError (client) ForbiddenError Access denied
|
* @apiError (client) ForbiddenError Access denied or connected as guest
|
||||||
* @apiError (client) ValidationError Validation error
|
* @apiError (client) ValidationError Validation error
|
||||||
* @apiPermission user
|
* @apiPermission user
|
||||||
* @apiVersion 1.5.0
|
* @apiVersion 1.5.0
|
||||||
@ -877,7 +896,7 @@ public class UserResource extends BaseResource {
|
|||||||
@POST
|
@POST
|
||||||
@Path("disable_totp")
|
@Path("disable_totp")
|
||||||
public Response disableTotp(@FormParam("password") String password) {
|
public Response disableTotp(@FormParam("password") String password) {
|
||||||
if (!authenticate()) {
|
if (!authenticate() || principal.isGuest()) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,17 @@ module.exports = function(grunt) {
|
|||||||
init: ['dist'],
|
init: ['dist'],
|
||||||
after: ['dist/style.css', 'dist/docs.js', 'dist/share.js', 'dist/less.css', 'dist/app']
|
after: ['dist/style.css', 'dist/docs.js', 'dist/share.js', 'dist/less.css', 'dist/app']
|
||||||
},
|
},
|
||||||
ngmin: {
|
ngAnnotate: {
|
||||||
|
options: {
|
||||||
|
singleQuotes: true
|
||||||
|
},
|
||||||
dist: {
|
dist: {
|
||||||
|
files: [{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'src',
|
cwd: 'src',
|
||||||
src: ['app/**/*.js'],
|
src: ['app/**/*.js'],
|
||||||
dest: 'dist'
|
dest: 'dist'
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
concat: {
|
concat: {
|
||||||
@ -110,12 +115,12 @@ module.exports = function(grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-htmlrefs');
|
grunt.loadNpmTasks('grunt-htmlrefs');
|
||||||
grunt.loadNpmTasks('grunt-css');
|
grunt.loadNpmTasks('grunt-css');
|
||||||
grunt.loadNpmTasks('grunt-contrib-less');
|
grunt.loadNpmTasks('grunt-contrib-less');
|
||||||
grunt.loadNpmTasks('grunt-ngmin');
|
grunt.loadNpmTasks('grunt-ng-annotate');
|
||||||
grunt.loadNpmTasks('grunt-text-replace');
|
grunt.loadNpmTasks('grunt-text-replace');
|
||||||
grunt.loadNpmTasks('grunt-apidoc');
|
grunt.loadNpmTasks('grunt-apidoc');
|
||||||
|
|
||||||
// Default tasks.
|
// Default tasks.
|
||||||
grunt.registerTask('default', ['clean:init', 'ngmin', 'concat:docs', 'concat:share', 'less', 'concat:css', 'cssmin',
|
grunt.registerTask('default', ['clean:init', 'ngAnnotate', 'concat:docs', 'concat:share', 'less', 'concat:css', 'cssmin',
|
||||||
'uglify:docs', 'uglify:share', 'copy', 'clean:after', 'cleanempty', 'htmlrefs:index', 'htmlrefs:share', 'replace', 'apidoc']);
|
'uglify:docs', 'uglify:share', 'copy', 'clean:after', 'cleanempty', 'htmlrefs:index', 'htmlrefs:share', 'replace', 'apidoc']);
|
||||||
|
|
||||||
};
|
};
|
@ -26,18 +26,33 @@
|
|||||||
<url-pattern>*.jsp</url-pattern>
|
<url-pattern>*.jsp</url-pattern>
|
||||||
</filter-mapping>
|
</filter-mapping>
|
||||||
|
|
||||||
<!-- This filter is used to secure URLs -->
|
<!-- These filters are used to secure URLs -->
|
||||||
<filter>
|
<filter>
|
||||||
<filter-name>tokenBasedSecurityFilter</filter-name>
|
<filter-name>tokenBasedSecurityFilter</filter-name>
|
||||||
<filter-class>com.sismics.util.filter.TokenBasedSecurityFilter</filter-class>
|
<filter-class>com.sismics.util.filter.TokenBasedSecurityFilter</filter-class>
|
||||||
<async-supported>true</async-supported>
|
<async-supported>true</async-supported>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||||
|
<filter-class>com.sismics.util.filter.HeaderBasedSecurityFilter</filter-class>
|
||||||
|
<async-supported>true</async-supported>
|
||||||
|
<init-param>
|
||||||
|
<param-name>enabled</param-name>
|
||||||
|
<param-value>false</param-value>
|
||||||
|
</init-param>
|
||||||
|
</filter>
|
||||||
|
|
||||||
<filter-mapping>
|
<filter-mapping>
|
||||||
<filter-name>tokenBasedSecurityFilter</filter-name>
|
<filter-name>tokenBasedSecurityFilter</filter-name>
|
||||||
<url-pattern>/api/*</url-pattern>
|
<url-pattern>/api/*</url-pattern>
|
||||||
</filter-mapping>
|
</filter-mapping>
|
||||||
|
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||||
|
<url-pattern>/api/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
<!-- Jersey -->
|
<!-- Jersey -->
|
||||||
<servlet>
|
<servlet>
|
||||||
<servlet-name>JerseyServlet</servlet-name>
|
<servlet-name>JerseyServlet</servlet-name>
|
||||||
|
46
docs-web/src/main/webapp/header.md
Normal file
46
docs-web/src/main/webapp/header.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
The web client and Android application for **Sismics Docs** are only examples
|
||||||
|
of what is possible with the provided REST API. Everything you see in those apps are
|
||||||
|
accessible using the API.
|
||||||
|
|
||||||
|
This documentation is divided in two parts. The first will get you started on essentials
|
||||||
|
steps like authentication and the second part is a full reference of every endpoints.
|
||||||
|
|
||||||
|
## API URL
|
||||||
|
The base URL depends on your server. If your instance of Docs is accessible through
|
||||||
|
`https://docs.mycompany.com`, then the base API URL is `https://docs.mycompany.com/api`.
|
||||||
|
|
||||||
|
## Verbs and status codes
|
||||||
|
The API uses restful verbs.
|
||||||
|
|
||||||
|
| Verb | Description |
|
||||||
|
|---|---|
|
||||||
|
| `GET` | Select one or more items |
|
||||||
|
| `PUT` | Create a new item |
|
||||||
|
| `POST` | Update an item |
|
||||||
|
| `DELETE` | Delete an item |
|
||||||
|
|
||||||
|
Successful calls return a HTTP code 200, anything else if an error.
|
||||||
|
|
||||||
|
## Dates
|
||||||
|
All dates are returned in UNIX timestamp format in milliseconds.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
#### **Step 1: [POST /user/login](#api-User-PostUserLogin)**
|
||||||
|
|
||||||
|
A call to this endpoint will return a cookie header like this:
|
||||||
|
```
|
||||||
|
HTTP Response:
|
||||||
|
Set-Cookie: auth_token=64085630-2ae6-415c-9a92-4b22c107eaa4
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 2: Authenticated API calls**
|
||||||
|
|
||||||
|
All following API calls must have a cookie header supplying the given token, like this:
|
||||||
|
```
|
||||||
|
HTTP Request:
|
||||||
|
Cookie: auth_token=64085630-2ae6-415c-9a92-4b22c107eaa4
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 3: [POST /user/logout](#api-User-PostUserLogout)**
|
||||||
|
|
||||||
|
A call to this API with a given `auth_token` cookie will make it unusable for other calls.
|
@ -28,7 +28,11 @@
|
|||||||
"App",
|
"App",
|
||||||
"Theme",
|
"Theme",
|
||||||
"Vocabulary"
|
"Vocabulary"
|
||||||
]
|
],
|
||||||
|
"header": {
|
||||||
|
"title": "Getting started",
|
||||||
|
"filename": "header.md"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt": "^1.0.1",
|
"grunt": "^1.0.1",
|
||||||
@ -41,7 +45,7 @@
|
|||||||
"grunt-contrib-uglify": "^1.0.1",
|
"grunt-contrib-uglify": "^1.0.1",
|
||||||
"grunt-css": "^0.5.4",
|
"grunt-css": "^0.5.4",
|
||||||
"grunt-htmlrefs": "^0.5.0",
|
"grunt-htmlrefs": "^0.5.0",
|
||||||
"grunt-ngmin": "0.0.3",
|
"grunt-ng-annotate": "^2.0.2",
|
||||||
"grunt-text-replace": "^0.4.0",
|
"grunt-text-replace": "^0.4.0",
|
||||||
"protractor": "^3.3.0",
|
"protractor": "^3.3.0",
|
||||||
"selenium": "^2.20.0"
|
"selenium": "^2.20.0"
|
||||||
|
@ -106,12 +106,12 @@ angular.module('docs',
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('settings.theme', {
|
.state('settings.config', {
|
||||||
url: '/theme',
|
url: '/config',
|
||||||
views: {
|
views: {
|
||||||
'settings': {
|
'settings': {
|
||||||
templateUrl: 'partial/docs/settings.theme.html',
|
templateUrl: 'partial/docs/settings.config.html',
|
||||||
controller: 'SettingsTheme'
|
controller: 'SettingsConfig'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3,12 +3,24 @@
|
|||||||
/**
|
/**
|
||||||
* Login controller.
|
* Login controller.
|
||||||
*/
|
*/
|
||||||
angular.module('docs').controller('Login', function($scope, $rootScope, $state, $dialog, User) {
|
angular.module('docs').controller('Login', function(Restangular, $scope, $rootScope, $state, $dialog, User) {
|
||||||
$scope.codeRequired = false;
|
$scope.codeRequired = false;
|
||||||
|
|
||||||
/**
|
// Get the app configuration
|
||||||
* Login.
|
Restangular.one('app').get().then(function(data) {
|
||||||
*/
|
$scope.app = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login as guest
|
||||||
|
$scope.loginAsGuest = function() {
|
||||||
|
$scope.user = {
|
||||||
|
username: 'guest',
|
||||||
|
password: ''
|
||||||
|
};
|
||||||
|
$scope.login();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Login
|
||||||
$scope.login = function() {
|
$scope.login = function() {
|
||||||
User.login($scope.user).then(function() {
|
User.login($scope.user).then(function() {
|
||||||
User.userInfo(true).then(function(data) {
|
User.userInfo(true).then(function(data) {
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings theme page controller.
|
* Settings config page controller.
|
||||||
*/
|
*/
|
||||||
angular.module('docs').controller('SettingsTheme', function($scope, $rootScope, Restangular) {
|
angular.module('docs').controller('SettingsConfig', function($scope, $rootScope, Restangular) {
|
||||||
|
// Get the app configuration
|
||||||
|
Restangular.one('app').get().then(function(data) {
|
||||||
|
$scope.app = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable/disable guest login
|
||||||
|
$scope.changeGuestLogin = function(enabled) {
|
||||||
|
Restangular.one('app').post('guest_login', {
|
||||||
|
enabled: enabled
|
||||||
|
}).then(function() {
|
||||||
|
$scope.app.guest_login = enabled;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch the current theme configuration
|
// Fetch the current theme configuration
|
||||||
Restangular.one('theme').get().then(function(data) {
|
Restangular.one('theme').get().then(function(data) {
|
||||||
$scope.theme = data;
|
$scope.theme = data;
|
@ -60,7 +60,7 @@
|
|||||||
<script src="app/docs/controller/settings/Settings.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/Settings.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsDefault.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsDefault.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsAccount.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsAccount.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsTheme.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsConfig.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsSecurity.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsSecurity.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsSecurityModalDisableTotp.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsSecurityModalDisableTotp.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/settings/SettingsSession.js" type="text/javascript"></script>
|
<script src="app/docs/controller/settings/SettingsSession.js" type="text/javascript"></script>
|
||||||
@ -125,12 +125,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#/settings/account" title="Logged in as {{ userInfo.username }}">
|
<a href="{{ userInfo.username == 'guest' ? '#/user/guest' : '#/settings/account' }}"
|
||||||
|
title="Logged in as {{ userInfo.username }}">
|
||||||
<span class="glyphicon glyphicon-user"></span>
|
<span class="glyphicon glyphicon-user"></span>
|
||||||
{{ userInfo.username }}
|
{{ userInfo.username }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li ng-class="{active: $uiRoute}" ui-route="/settings.*">
|
<li ng-class="{active: $uiRoute}" ui-route="/settings.*" ng-show="userInfo.username != 'guest'">
|
||||||
<a href="#/settings/account">
|
<a href="#/settings/account">
|
||||||
<span class="glyphicon glyphicon-cog"></span> Settings
|
<span class="glyphicon glyphicon-cog"></span> Settings
|
||||||
</a>
|
</a>
|
||||||
|
@ -46,6 +46,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr ng-if="!documents">
|
||||||
|
<td colspan="3" class="text-center">
|
||||||
|
<img src="img/loader.gif" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr ng-if="totalDocuments == 0">
|
||||||
|
<td colspan="3" class="text-center">
|
||||||
|
<span ng-if="search.length == 0">No document in the database</span>
|
||||||
|
<span ng-if="search.length > 0">No matches for <strong>"{{ search }}"</strong></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents" ng-class="{ active: $stateParams.id == document.id }">
|
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents" ng-class="{ active: $stateParams.id == document.id }">
|
||||||
<td>
|
<td>
|
||||||
{{ document.title }} ({{ document.file_count }})
|
{{ document.title }} ({{ document.file_count }})
|
||||||
@ -80,7 +93,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right" >
|
<div class="text-right" >
|
||||||
|
<span ng-if="totalDocuments">
|
||||||
{{ totalDocuments }} document{{ totalDocuments > 1 ? 's' : '' }} found
|
{{ totalDocuments }} document{{ totalDocuments > 1 ? 's' : '' }} found
|
||||||
|
</span>
|
||||||
|
<span ng-if="!totalDocuments"> </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,6 +35,12 @@
|
|||||||
<button type="submit" class="btn btn-primary btn-block" ng-click="login()">
|
<button type="submit" class="btn btn-primary btn-block" ng-click="login()">
|
||||||
<span class="glyphicon glyphicon-ok"></span> Sign in
|
<span class="glyphicon glyphicon-ok"></span> Sign in
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<p class="text-center lead" ng-if="app.guest_login"> </p>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-default btn-block" ng-if="app.guest_login" ng-click="loginAsGuest()">
|
||||||
|
<span class="glyphicon glyphicon-user"></span> Login as guest
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,3 +1,18 @@
|
|||||||
|
<h1>
|
||||||
|
Guest <small>access</small>
|
||||||
|
<span class="label" ng-class="{ 'label-success': app.guest_login, 'label-danger': !app.guest_login }">
|
||||||
|
{{ app.guest_login ? 'Enabled' : 'Disabled' }}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
Guest access is a mode where anyone can access {{ appName }} without password.<br/>
|
||||||
|
Like a normal user, the guest user can only access its documents and those accessible through permissions.<br/>
|
||||||
|
</p>
|
||||||
|
<div ng-if="app">
|
||||||
|
<button ng-if="!app.guest_login" class="btn btn-primary" ng-click="changeGuestLogin(true)">Enable guest access</button>
|
||||||
|
<button ng-if="app.guest_login" class="btn btn-danger" ng-click="changeGuestLogin(false)">Disable guest access</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Theme <small>customization</small></h1>
|
<h1>Theme <small>customization</small></h1>
|
||||||
<form class="form-horizontal" name="editColorForm" novalidate>
|
<form class="form-horizontal" name="editColorForm" novalidate>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
@ -1,4 +1,4 @@
|
|||||||
<h1>Groups <small>management</small> <a class="btn btn-primary" href="#/settings/group/add">Add</a></h1>
|
<h1>Groups <small>management</small> <a class="btn btn-primary" href="#/settings/group/add">Add a group</a></h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 well">
|
<div class="col-md-4 well">
|
||||||
|
@ -9,14 +9,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default" ng-show="isAdmin">
|
||||||
<div class="panel-heading" ng-show="isAdmin"><strong>General settings</strong></div>
|
<div class="panel-heading"><strong>General settings</strong></div>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<a class="list-group-item" ng-show="isAdmin" ng-class="{active: $uiRoute}" ui-route="/settings/user.*" href="#/settings/user">Users</a>
|
<a class="list-group-item" ng-class="{active: $uiRoute}" ui-route="/settings/user.*" href="#/settings/user">Users</a>
|
||||||
<a class="list-group-item" ng-show="isAdmin" ng-class="{active: $uiRoute}" ui-route="/settings/group.*" href="#/settings/group">Groups</a>
|
<a class="list-group-item" ng-class="{active: $uiRoute}" ui-route="/settings/group.*" href="#/settings/group">Groups</a>
|
||||||
<a class="list-group-item" ng-show="isAdmin" ng-class="{active: $uiRoute}" ui-route="/settings/vocabulary.*" href="#/settings/vocabulary">Vocabularies</a>
|
<a class="list-group-item" ng-class="{active: $uiRoute}" ui-route="/settings/vocabulary.*" href="#/settings/vocabulary">Vocabularies</a>
|
||||||
<a class="list-group-item" ng-show="isAdmin" ng-class="{active: $uiRoute}" ui-route="/settings/theme" href="#/settings/theme">Theme</a>
|
<a class="list-group-item" ng-class="{active: $uiRoute}" ui-route="/settings/config" href="#/settings/config">Configuration</a>
|
||||||
<a class="list-group-item" ng-show="isAdmin" ng-class="{active: $uiRoute}" ui-route="/settings/log" href="#/settings/log">Server logs</a>
|
<a class="list-group-item" ng-class="{active: $uiRoute}" ui-route="/settings/log" href="#/settings/log">Server logs</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,10 +13,12 @@
|
|||||||
<td>{{ session.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
<td>{{ session.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||||
<td>{{ session.last_connection_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
<td>{{ session.last_connection_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||||
<td title="{{ session.user_agent }}">{{ session.ip }}</td>
|
<td title="{{ session.user_agent }}">{{ session.ip }}</td>
|
||||||
<td><span ng-show="session.current" class="glyphicon glyphicon-ok"></span></td>
|
<td>
|
||||||
|
<span ng-show="session.current" class="glyphicon glyphicon-ok" title="This is the current session"></span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit" class="btn btn-warning" ng-click="deleteSession()">Clear all other sessions</button>
|
<button type="submit" class="btn btn-warning" title="All other devices connected to this account will be disconnected" ng-click="deleteSession()">Clear all other sessions</button>
|
||||||
</div>
|
</div>
|
@ -62,7 +62,7 @@
|
|||||||
<span class="help-block" ng-show="editUserForm.storage_quota.$error.pattern">Number required</span>
|
<span class="help-block" ng-show="editUserForm.storage_quota.$error.pattern">Number required</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-class="{ 'has-error': !editUserForm.password.$valid, success: editUserForm.password.$valid }">
|
<div class="form-group" ng-if="user.username != 'guest'" ng-class="{ 'has-error': !editUserForm.password.$valid, success: editUserForm.password.$valid }">
|
||||||
<label class="col-sm-2 control-label" for="inputPassword">Password</label>
|
<label class="col-sm-2 control-label" for="inputPassword">Password</label>
|
||||||
|
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
@ -76,7 +76,7 @@
|
|||||||
<span class="help-block" ng-show="editUserForm.password.$error.maxlength">Too long</span>
|
<span class="help-block" ng-show="editUserForm.password.$error.maxlength">Too long</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-class="{ 'has-error': !editUserForm.passwordconfirm.$valid, success: editUserForm.passwordconfirm.$valid }">
|
<div class="form-group" ng-if="user.username != 'guest'" ng-class="{ 'has-error': !editUserForm.passwordconfirm.$valid, success: editUserForm.passwordconfirm.$valid }">
|
||||||
<label class="col-sm-2 -label" for="inputPasswordConfirm">Password (confirm)</label>
|
<label class="col-sm-2 -label" for="inputPasswordConfirm">Password (confirm)</label>
|
||||||
|
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
@ -94,7 +94,7 @@
|
|||||||
<button type="submit" class="btn btn-primary" ng-click="edit()" ng-disabled="!editUserForm.$valid">
|
<button type="submit" class="btn btn-primary" ng-click="edit()" ng-disabled="!editUserForm.$valid">
|
||||||
<span class="glyphicon glyphicon-pencil"></span> {{ isEdit() ? 'Edit' : 'Add' }}
|
<span class="glyphicon glyphicon-pencil"></span> {{ isEdit() ? 'Edit' : 'Add' }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-danger" ng-click="remove()" ng-show="isEdit()">
|
<button type="button" class="btn btn-danger" ng-click="remove()" ng-show="isEdit() && user.username != 'guest'">
|
||||||
<span class="glyphicon glyphicon-trash"></span> Delete
|
<span class="glyphicon glyphicon-trash"></span> Delete
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h1>Users <small>management</small> <a class="btn btn-primary" href="#/settings/user/add">Add</a></h1>
|
<h1>Users <small>management</small> <a class="btn btn-primary" href="#/settings/user/add">Add a user</a></h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 well">
|
<div class="col-md-4 well">
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=9
|
db.version=10
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=9
|
db.version=10
|
@ -1,5 +1,13 @@
|
|||||||
package com.sismics.docs.rest;
|
package com.sismics.docs.rest;
|
||||||
|
|
||||||
|
import com.sismics.docs.core.constant.PermType;
|
||||||
|
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||||
|
import com.sismics.util.context.ThreadLocalContext;
|
||||||
|
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||||
|
import com.sismics.util.jpa.EMF;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.json.JsonArray;
|
import javax.json.JsonArray;
|
||||||
import javax.json.JsonObject;
|
import javax.json.JsonObject;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
@ -9,15 +17,6 @@ import javax.ws.rs.core.Form;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
import com.sismics.docs.core.constant.PermType;
|
|
||||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
|
||||||
import com.sismics.util.context.ThreadLocalContext;
|
|
||||||
import com.sismics.util.jpa.EMF;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the app resource.
|
* Test the app resource.
|
||||||
@ -35,16 +34,14 @@ public class TestAppResource extends BaseJerseyTest {
|
|||||||
|
|
||||||
// Check the application info
|
// Check the application info
|
||||||
JsonObject json = target().path("/app").request()
|
JsonObject json = target().path("/app").request()
|
||||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
|
||||||
.get(JsonObject.class);
|
.get(JsonObject.class);
|
||||||
String currentVersion = json.getString("current_version");
|
Assert.assertNotNull(json.getString("current_version"));
|
||||||
Assert.assertNotNull(currentVersion);
|
Assert.assertNotNull(json.getString("min_version"));
|
||||||
String minVersion = json.getString("min_version");
|
|
||||||
Assert.assertNotNull(minVersion);
|
|
||||||
Long freeMemory = json.getJsonNumber("free_memory").longValue();
|
Long freeMemory = json.getJsonNumber("free_memory").longValue();
|
||||||
Assert.assertTrue(freeMemory > 0);
|
Assert.assertTrue(freeMemory > 0);
|
||||||
Long totalMemory = json.getJsonNumber("total_memory").longValue();
|
Long totalMemory = json.getJsonNumber("total_memory").longValue();
|
||||||
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
||||||
|
Assert.assertFalse(json.getBoolean("guest_login"));
|
||||||
|
|
||||||
// Rebuild Lucene index
|
// Rebuild Lucene index
|
||||||
Response response = target().path("/app/batch/reindex").request()
|
Response response = target().path("/app/batch/reindex").request()
|
||||||
@ -127,4 +124,69 @@ public class TestAppResource extends BaseJerseyTest {
|
|||||||
Long date4 = logs.getJsonObject(9).getJsonNumber("date").longValue();
|
Long date4 = logs.getJsonObject(9).getJsonNumber("date").longValue();
|
||||||
Assert.assertTrue(date3 >= date4);
|
Assert.assertTrue(date3 >= date4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the guest login.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGuestLogin() {
|
||||||
|
// Login admin
|
||||||
|
String adminToken = clientUtil.login("admin", "admin", false);
|
||||||
|
|
||||||
|
// Try to login as guest
|
||||||
|
Response response = target().path("/user/login").request()
|
||||||
|
.post(Entity.form(new Form()
|
||||||
|
.param("username", "guest")));
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Enable guest login
|
||||||
|
target().path("/app/guest_login").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||||
|
.post(Entity.form(new Form()
|
||||||
|
.param("enabled", "true")), JsonObject.class);
|
||||||
|
|
||||||
|
// Login as guest
|
||||||
|
String guestToken = clientUtil.login("guest", "", false);
|
||||||
|
|
||||||
|
// Guest cannot delete himself
|
||||||
|
response = target().path("/user").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.delete();
|
||||||
|
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Guest cannot see opened sessions
|
||||||
|
JsonObject json = target().path("/user/session").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.get(JsonObject.class);
|
||||||
|
Assert.assertEquals(0, json.getJsonArray("sessions").size());
|
||||||
|
|
||||||
|
// Guest cannot delete opened sessions
|
||||||
|
response = target().path("/user/session").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.delete();
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Guest cannot enable TOTP
|
||||||
|
response = target().path("/user/enable_totp").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.post(Entity.form(new Form()));
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Guest cannot disable TOTP
|
||||||
|
response = target().path("/user/disable_totp").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.post(Entity.form(new Form()));
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Guest cannot update itself
|
||||||
|
response = target().path("/user").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.post(Entity.form(new Form()));
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
|
||||||
|
|
||||||
|
// Guest can see its documents
|
||||||
|
target().path("/document/list").request()
|
||||||
|
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||||
|
.get(JsonObject.class);
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import javax.ws.rs.core.Form;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
|
import com.sismics.util.filter.HeaderBasedSecurityFilter;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@ -28,7 +29,7 @@ public class TestSecurity extends BaseJerseyTest {
|
|||||||
clientUtil.createUser("testsecurity");
|
clientUtil.createUser("testsecurity");
|
||||||
|
|
||||||
// Changes a user's email KO : the user is not connected
|
// Changes a user's email KO : the user is not connected
|
||||||
Response response = target().path("/user/update").request()
|
Response response = target().path("/user").request()
|
||||||
.post(Entity.form(new Form().param("email", "testsecurity2@docs.com")));
|
.post(Entity.form(new Form().param("email", "testsecurity2@docs.com")));
|
||||||
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
||||||
JsonObject json = response.readEntity(JsonObject.class);
|
JsonObject json = response.readEntity(JsonObject.class);
|
||||||
@ -73,4 +74,29 @@ public class TestSecurity extends BaseJerseyTest {
|
|||||||
// User testsecurity logs out
|
// User testsecurity logs out
|
||||||
clientUtil.logout(testSecurityToken);
|
clientUtil.logout(testSecurityToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeaderBasedAuthentication() {
|
||||||
|
clientUtil.createUser("header_auth_test");
|
||||||
|
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), target()
|
||||||
|
.path("/user/session")
|
||||||
|
.request()
|
||||||
|
.get()
|
||||||
|
.getStatus());
|
||||||
|
|
||||||
|
Assert.assertEquals(Status.OK.getStatusCode(), target()
|
||||||
|
.path("/user/session")
|
||||||
|
.request()
|
||||||
|
.header(HeaderBasedSecurityFilter.AUTHENTICATED_USER_HEADER, "header_auth_test")
|
||||||
|
.get()
|
||||||
|
.getStatus());
|
||||||
|
|
||||||
|
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), target()
|
||||||
|
.path("/user/session")
|
||||||
|
.request()
|
||||||
|
.header(HeaderBasedSecurityFilter.AUTHENTICATED_USER_HEADER, "idontexist")
|
||||||
|
.get()
|
||||||
|
.getStatus());
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ hibernate.connection.username=sa
|
|||||||
hibernate.connection.password=
|
hibernate.connection.password=
|
||||||
hibernate.hbm2ddl.auto=
|
hibernate.hbm2ddl.auto=
|
||||||
hibernate.dialect=org.hibernate.dialect.HSQLDialect
|
hibernate.dialect=org.hibernate.dialect.HSQLDialect
|
||||||
hibernate.show_sql=true
|
hibernate.show_sql=false
|
||||||
hibernate.format_sql=false
|
hibernate.format_sql=false
|
||||||
hibernate.max_fetch_depth=5
|
hibernate.max_fetch_depth=5
|
||||||
hibernate.cache.use_second_level_cache=false
|
hibernate.cache.use_second_level_cache=false
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
log4j.rootCategory=DEBUG, CONSOLE, MEMORY
|
log4j.rootCategory=INFO, CONSOLE, MEMORY
|
||||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
||||||
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||||
log4j.appender.MEMORY.size=1000
|
log4j.appender.MEMORY.size=1000
|
||||||
|
|
||||||
log4j.logger.com.sismics=DEBUG
|
log4j.logger.com.sismics=INFO
|
||||||
log4j.logger.org.hibernate=INFO
|
log4j.logger.org.hibernate=INFO
|
||||||
log4j.logger.org.apache.pdfbox=INFO
|
log4j.logger.org.apache.pdfbox=INFO
|
Loading…
Reference in New Issue
Block a user