1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-28 03:10:45 +00:00

Add anonymous share feature

This commit is contained in:
xiez 2012-06-12 10:13:14 +08:00
parent 82263b6787
commit 705f86e68d
23 changed files with 571 additions and 426 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.pyc
*~
*#
seahub.db*
local_settings.py
startserver.sh

View File

@ -62,3 +62,14 @@ def translate_commit_time(value):
return u'%d 分钟前' % (seconds/60)
else:
return u'%d 秒前' % (seconds)
@register.filter(name='translate_remain_time')
def translate_remain_time(value):
if value > 24 * 60 * 60:
return u'%d' % (value/24/3600)
elif value > 60 * 60:
return u'%d 小时' % (value/3600)
elif value > 60:
return u'%d 分钟' % (value/60)
else:
return u'%d' % (value)

View File

@ -32,7 +32,7 @@ def contact_add(request):
emailuser = ccnet_rpc.get_emailuser(contact_email)
if not emailuser:
error_msg = u"用户不存在"
elif cmp(contact_email, request.user.username) == 0:
elif contact_email == request.user.username:
error_msg = u"不能添加自己为联系人"
elif Contact.objects.filter(user_email=request.user.username,
contact_email=contact_email).count() > 0:

View File

@ -324,7 +324,8 @@ h2.repo-history {
padding:0 0 6px 0;
}
/*repo-share-form*/
#to_email,
#email_or_group,
#share-link,
#added-member-name {
width:260px;
height:80px;

View File

@ -65,6 +65,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.csrf.CsrfResponseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'auth.middleware.AuthenticationMiddleware',
'seahub.base.middleware.InfobarMiddleware',
# 'seahub.base.middleware.UseridMiddleware',
@ -89,6 +90,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.media',
'djblets.util.context_processors.siteRoot',
'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
'seahub.base.context_processors.base',
)
@ -99,6 +101,7 @@ INSTALLED_APPS = (
'django.contrib.sessions',
# 'django.contrib.sites',
# 'django.contrib.admin',
'django.contrib.messages',
'registration',
'avatar',
'seahub.notifications',
@ -106,7 +109,7 @@ INSTALLED_APPS = (
'seahub.profile',
'seahub.contacts',
'seahub.group',
# 'seahub.share',
'seahub.share',
)
AUTHENTICATION_BACKENDS = (

View File

@ -1,43 +1,9 @@
from django import forms
from django.contrib.auth.models import User
class GroupAddRepoForm(forms.Form):
class RepoShareForm(forms.Form):
"""
Form for adding repo to a group.
Form for sharing repo to user or group.
"""
email_or_group = forms.CharField(max_length=512)
repo_id = forms.CharField(max_length=36)
def __init__(self, *args, **kwargs):
super(GroupAddRepoForm, self).__init__(*args, **kwargs)
class UserShareForm(forms.Form):
"""
Form for sharing repo to a user.
"""
user_email = forms.EmailField()
repo_id = forms.CharField(max_length=36)
def __init__(self, *args, **kwargs):
super(UserShareForm, self).__init__(*args, **kwargs)
def clean_user_email(self):
data = self.cleaned_data['user_email']
try:
# put the user in form.to_user for further use
self.to_user = User.objects.get(email=data)
except User.DoesNotExist:
raise forms.ValidationError("No user with such email")
return data
def clean_repo_id(self):
data = self.cleaned_data['repo_id']
if len(data) != 36:
raise forms.ValidationError("Invalid repo id")
return data

View File

@ -1,19 +1,8 @@
from django.db import models
from django.contrib.auth.models import User
class UserShare(models.Model):
"""Record a repo shared to a user."""
from_user = models.ForeignKey(User, related_name="myshare_items")
to_user = models.ForeignKey(User, related_name="share2me_items")
class AnonymousShare(models.Model):
repo_owner = models.EmailField(max_length=255)
repo_id = models.CharField(max_length=36)
class GroupShare(models.Model):
"""A repo shared to a group."""
group_id = models.CharField(max_length=36)
repo_id = models.CharField(max_length=36)
class Meta:
unique_together = ("group_id", "repo_id")
anonymous_email = models.EmailField(max_length=255)
token = models.CharField(max_length=25, unique=True)

4
share/settings.py Normal file
View File

@ -0,0 +1,4 @@
from django.conf import settings
ANONYMOUS_SHARE_COOKIE_TIMEOUT = getattr(settings, 'ANONYMOUS_SHARE_COOKIE_TIMEOUT', 24*60*60)
ANONYMOUS_SHARE_LINK_TIMEOUT = getattr(settings, 'ANONYMOUS_SHARE_LINK_TIMEOUT', 2)

View File

@ -0,0 +1,9 @@
{% extends "myhome_base.html" %}
{% block title %}{% endblock %}
{% block main_panel %}
<div class="text-panel">
<p>该访问链接已失效,如有需要,请联系目录拥有者 {{ repo_owner }}</p>
</div>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% autoescape off %}
亲爱的:{{ anon_email }} 您好!
{{ email }} 在SeaCloud上共享了一个同步目录给你请点击以下链接查看
{{ protocol }}://{{ domain }}{% url share.views.anonymous_share_confirm token=token %}
感谢使用我们的网站!
Seafile团队
{% endautoescape %}

View File

@ -1,4 +1,5 @@
{% extends "myhome_base.html" %}
{% load seahub_tags %}
{% block nav_shareadmin_class %}class="cur"{% endblock %}
@ -30,6 +31,37 @@
{% else %}
<p>暂无</p>
{% endif %}
<h3>我管理的共享链接</h3>
{% if out_links %}
<table class="link-list">
<tr>
<th width="20%">名字</th>
<th width="30%">共享给</th>
<th width="30%">有效期</th>
<th width="20%">操作</th>
</tr>
{% for link in out_links %}
<tr>
<td><a href="{{ SITE_ROOT }}repo/{{ repo.id }}">{{ link.repo_name }}</a></td>
<td>{{ link.anonymous_email }}</td>
{% if link.remain_time %}
<td>{{ link.remain_time|translate_remain_time }}</td>
{% else %}
<td>已过期</td>
{% endif %}
<td><a class="op" href="#" class="view-link" data="{{ link.token }}">查看链接</a>
<a class="op" href="{{ SITE_ROOT }}share/remove/{{ link.token }}/">删除</a></td>
</tr>
{% endfor %}
</table>
{% endif %}
<form id="view-link-form" name="view-link-form" class="hide">
<textarea id="share-link" name="share-link"></textarea>
</form>
{% endblock %}
{% block extra_script %}
@ -46,5 +78,14 @@ $("table tr:gt(0)").hover(
$(this).find('img').addClass('vh');
}
);
$(".view-link").click(function() {
var t = $(this).attr('data');
var l = '{{ protocol }}://' + '{{ domain }}{{ SITE_ROOT }}share/' + t + '/';
$('textarea[name="share-link"]').val(l);
$("#view-link-form").modal({appendTo: "#main", containerCss:{padding:18}});
return false;
});
</script>
{% endblock %}

View File

@ -1,25 +0,0 @@
{% extends "myhome_base.html" %}
{% block left_panel %}
<ul>
<li><a href="{{ SITE_ROOT }}share/list/">共享列表</a></li>
<li>共享同步目录</li>
</ul>
{% endblock %}
{% block right_panel %}
<h2>共享同步目录</h2>
<form action="" method="post">
{{ form.non_field_errors }}
{{ form.user_email.errors }}
<label>联系人 Email:</label><br/>
{{ form.user_email }}<br />
{{ form.repo_id.errors }}
<label>同步目录 ID:</label><br/>
{{ form.repo_id }}<br />
<input type="submit" value="Submit" />
</form>
{% endblock %}

View File

@ -1,46 +0,0 @@
{% extends "myhome_base.html" %}
{% block left_panel %}
<ul>
<li>共享列表</li>
<li><a href="{{ SITE_ROOT }}share/add/">共享同步目录</a></li>
</ul>
{% endblock %}
{% block right_panel %}
<h2>共享给我的同步目录</h2>
<table class="repo-list default">
<tr>
<th>共享人</th>
<th>ID</th>
<th>操作</th>
</tr>
{% for item in share2me_items %}
<tr>
<td>{{ item.from_user.email }}</td>
<td><a href="{{ SITE_ROOT }}repo/{{ item.repo_id }}/">{{ item.repo_id }}</a></td>
<td>
</td>
</tr>
{% endfor %}
</table>
<h2>所有共享的同步目录</h2>
<table class="repo-list default">
<tr>
<th>共享给</th>
<th>ID</th>
<th>操作</th>
</tr>
{% for item in share_items %}
<tr>
<td>{{ item.to_user.email }}</td>
<td><a href="{{ SITE_ROOT }}repo/{{ item.repo_id }}/">{{ item.repo_id }}</a></td>
<td><a href="{{ SITE_ROOT }}share/delete/{{ item.id }}/">删除</a>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

87
share/tokens.py Normal file
View File

@ -0,0 +1,87 @@
import random
from datetime import date
from datetime import datetime as dt
from django.conf import settings
from django.utils.http import int_to_base36, base36_to_int
from settings import ANONYMOUS_SHARE_LINK_TIMEOUT
class AnonymousShareTokenGenerator(object):
"""
Strategy object used to generate and check tokens for the repo anonymous
share mechanism.
"""
def make_token(self):
"""
Returns a token that can be used once to do a anonymous share for repo.
"""
return self._make_token_with_timestamp(self._num_days(self._today()))
def check_token(self, token):
"""
Check that a anonymous share token is valid.
"""
# Parse the token
try:
ts_b36, hash = token.split("-")
except ValueError:
return False
try:
ts = base36_to_int(ts_b36)
except ValueError:
return False
# Check the timestamp is within limit
if (self._num_days(self._today()) - ts) > ANONYMOUS_SHARE_LINK_TIMEOUT:
return False
return True
def get_remain_time(self, token):
"""
Get token remain time.
"""
try:
ts_b36, hash = token.split("-")
except ValueError:
return None
try:
ts = base36_to_int(ts_b36)
except ValueError:
return None
days = ANONYMOUS_SHARE_LINK_TIMEOUT - (self._num_days(self._today()) - ts)
if days < 0:
return None
now = dt.now()
tomorrow = dt(now.year, now.month, now.day+1)
return (tomorrow - now).seconds + days * 24 * 60 * 60
def _make_token_with_timestamp(self, timestamp):
# timestamp is number of days since 2001-1-1. Converted to
# base 36, this gives us a 3 digit string until about 2121
ts_b36 = int_to_base36(timestamp)
# We limit the hash to 20 chars to keep URL short
from django.utils.hashcompat import sha_constructor
import datetime
now = datetime.datetime.now()
hash = sha_constructor(settings.SECRET_KEY +
unicode(random.randint(0, 999999)) +
now.strftime('%Y-%m-%d %H:%M:%S') +
unicode(timestamp)).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash)
def _num_days(self, dt):
return (dt - date(2001,1,1)).days
def _today(self):
# Used for mocking in tests
return date.today()
anon_share_token_generator = AnonymousShareTokenGenerator()

View File

@ -2,10 +2,8 @@ from django.conf.urls.defaults import *
from views import *
urlpatterns = patterns('',
url(r'^$', list_shared_repos),
url(r'^list/$', list_shared_repos, name='shared_repo_list'),
url(r'^add/$', share_repo),
url(r'^delete/(?P<item_id>[^/]+)/$', delete_share_item),
url('^remove/(?P<token>.+)/$', remove_anonymous_share, name='remove_anonymous_share'),
url('^(?P<token>.+)/$', anonymous_share_confirm, name='anonymous_share_confirm'),
)

View File

@ -1,62 +1,221 @@
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
# encoding: utf-8
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response
from django.template import Context, loader, RequestContext
from forms import UserShareForm
from models import UserShare
from auth.decorators import login_required
from django.contrib import messages
from django.contrib.sites.models import Site, RequestSite
from pysearpc import SearpcError
from seaserv import seafserv_threaded_rpc, get_repo, ccnet_rpc
@login_required
def list_shared_repos(request):
"""Show the repos I shared."""
share_items = UserShare.objects.filter(from_user=request.user)
share2me_items = UserShare.objects.filter(to_user=request.user)
#for repo in s_repos:
# s_repos
# pass
return render_to_response("repo/shared_repo_list.html",
{ 'share_items': share_items,
"share2me_items": share2me_items },
context_instance=RequestContext(request))
from forms import RepoShareForm
from models import AnonymousShare
#from seahub.contacts.models import Contact
from seahub.views import validate_owner, validate_emailuser
from seahub.utils import go_permission_error
from settings import ANONYMOUS_SHARE_COOKIE_TIMEOUT
from tokens import anon_share_token_generator
@login_required
def share_repo(request):
"""Share a repo to a user."""
if request.method == 'POST':
form = UserShareForm(request.POST)
if form.is_valid():
repo_share = UserShare()
repo_share.from_user = request.user
repo_share.to_user = form.to_user
repo_share.repo_id = form.cleaned_data['repo_id']
try:
repo_share.save()
except IntegrityError:
# catch the case repo added to group before
pass
return HttpResponseRedirect(reverse('shared_repo_list', args=[]))
else:
user_email = request.REQUEST.get('user_email', '')
repo_id = request.REQUEST.get('repo_id', '')
form = UserShareForm(initial={'user_email': user_email, 'repo_id': repo_id})
"""
Handle repo share request
"""
if request.method != 'POST':
raise Http404
return render_to_response("repo/share_repo.html", {
'form': form,
}, context_instance=RequestContext(request))
form = RepoShareForm(request.POST)
if not form.is_valid():
# TODO: may display error msg on form
raise Http404
email_or_group = form.cleaned_data['email_or_group']
repo_id = form.cleaned_data['repo_id']
from_email = request.user.username
# Test whether user is the repo owner
if not validate_owner(request, repo_id):
return go_permission_error(request, u'只有目录拥有者有权共享目录')
# Handle the diffent separator
to_email_str = email_or_group.replace(';',',')
to_email_str = to_email_str.replace('\n',',')
to_email_str = to_email_str.replace('\r',',')
to_email_list = to_email_str.split(',')
for to_email in to_email_list:
to_email = to_email.strip(' ')
if not to_email:
continue
# if to_email is user name, the format is: 'example@mail.com';
# if to_email is group, the format is 'group_name <creator@mail.com>'
if (to_email.split(' ')[0].find('@') == -1):
# share repo to group
# TODO: if we know group id, then we can simplly call group_share_repo
if len(to_email.split(' ')) < 2:
messages.add_message(request, messages.ERROR, to_email)
continue
group_name = to_email.split(' ')[0]
group_creator = to_email.split(' ')[1]
# get all the groups the user joined
groups = ccnet_rpc.get_groups(request.user.username)
find = False
for group in groups:
# for every group that user joined, if group name and
# group creator matchs, then has find the group
if group.props.group_name == group_name and \
group_creator.find(group.props.creator_name) >= 0:
from seahub.group.views import group_share_repo
group_share_repo(request, repo_id, int(group.props.id),
from_email)
find = True
messages.add_message(request, messages.INFO, group_name)
break
if not find:
messages.add_message(request, messages.ERROR, group_name)
else:
if validate_emailuser(to_email):
# share repo to registered user
try:
seafserv_threaded_rpc.add_share(repo_id, from_email,
to_email, 'rw')
messages.add_message(request, messages.INFO, to_email)
except SearpcError, e:
messages.add_message(request, messages.ERROR, to_email)
# else:
# # add email to contacts if not in contacts list
# # TODO: condition should be removed
# if from_email != to_email and Contact.objects.filter(user_email=from_email, contact_email=to_email).count() = 0:
# contact = Contact()
# contact.user_email = from_email
# contact.contact_email = to_email
# contact.contact_name = ''
# contact.note = ''
# contact.save()
else:
# share repo to anonymous user
kwargs = {'repo_id': repo_id,
'repo_owner': from_email,
'anon_email': to_email
}
anonymous_share(request, **kwargs)
return HttpResponseRedirect(reverse('myhome'))
@login_required
def delete_share_item(request, item_id):
"""Delete a share item."""
def share_admin(request):
"""
List repos I share to others or groups, and list my anonymous share links
"""
username = request.user.username
# repos that are share to user
out_repos = seafserv_threaded_rpc.list_share_repos(username, 'from_email', -1, -1)
# repos that are share to groups
group_repos = seafserv_threaded_rpc.get_group_my_share_repos(request.user.username)
for group_repo in group_repos:
repo_id = group_repo.props.repo_id
if not repo_id:
continue
repo = get_repo(repo_id)
if not repo:
continue
group_id = group_repo.props.group_id
group = ccnet_rpc.get_group(int(group_id))
if not group:
continue
repo.props.shared_email = group.props.group_name
repo.gid = group_id
out_repos.append(repo)
# anonymous share links
out_links = AnonymousShare.objects.filter(repo_owner=request.user.username)
for link in out_links:
repo = get_repo(link.repo_id)
link.repo_name = repo.name
link.remain_time = anon_share_token_generator.get_remain_time(link.token)
return render_to_response('repo/share_admin.html', {
"out_repos": out_repos,
"out_links": out_links,
"protocol": request.is_secure() and 'https' or 'http',
"domain": RequestSite(request).domain,
}, context_instance=RequestContext(request))
# 2 views for anonymous share:
# - anonymous_share records share infomation to db and sends the mail
# - anonymous_share_confirm checks the link use clicked and
# adds token to client COOKIE, then redirect client to repo page
def anonymous_share(request, email_template_name='repo/anonymous_share_email.html', **kwargs):
repo_id = kwargs['repo_id']
repo_owner = kwargs['repo_owner']
anon_email = kwargs['anon_email']
token = anon_share_token_generator.make_token()
anon_share = AnonymousShare()
anon_share.repo_owner = repo_owner
anon_share.repo_id = repo_id
anon_share.anonymous_email = anon_email
anon_share.token = token
try:
item = UserShare.objects.get(pk=item_id)
item.delete()
except UserShare.DoesNotExist:
pass
anon_share.save()
except:
messages.add_message(request, messages.ERROR, kwargs['anon_email'])
else:
# send mail
use_https = request.is_secure()
site_name = domain = RequestSite(request).domain
t = loader.get_template(email_template_name)
c = {
'email': repo_owner,
'anon_email': anon_email,
'domain': domain,
'site_name': site_name,
'token': token,
'protocol': use_https and 'https' or 'http',
}
try:
send_mail(u'您在SeaCloud上收到一个同步目录', t.render(Context(c)), None,
[anon_email], fail_silently=False)
except:
AnonymousShare.objects.filter(token=token).delete()
messages.add_message(request, messages.ERROR, anon_email)
else:
messages.add_message(request, messages.INFO, anon_email)
def anonymous_share_confirm(request, token=None):
assert token is not None # checked by URLconf
# Check whether token in db
try:
anon_share = AnonymousShare.objects.get(token=token)
except AnonymousShare.DoesNotExist:
raise Http404
context_instance = RequestContext(request)
context_instance['repo_owner'] = anon_share.repo_owner
if anon_share_token_generator.check_token(token):
res = HttpResponseRedirect(reverse('repo', args=[anon_share.repo_id]))
res.set_cookie("anontoken", token,
max_age=ANONYMOUS_SHARE_COOKIE_TIMEOUT)
return res
else:
return render_to_response('repo/anonymous_share_confirm.html',
context_instance=context_instance)
def remove_anonymous_share(request, token):
AnonymousShare.objects.filter(token=token).delete()
return HttpResponseRedirect(reverse('share_admin'))
return HttpResponseRedirect(request.META['HTTP_REFERER'])

View File

@ -13,13 +13,15 @@
<body>
<div id="wrapper">
{% block info_bar_message %}
{% if request.user.is_authenticated and request.cur_note %}
<div id="info-bar">
<span class="info">{{ request.cur_note.message|urlize }}</span>
<a href="{{ SITE_ROOT }}notification/close/{{ request.cur_note.id }}/?next={{ request.path }}"><span class="close"></span></a>
</div>
{% endif %}
{% endblock info_bar_message %}
<div id="top-bar">
<div class="top-bar">
<div class="top-bar-in w100 ovhd">

View File

@ -39,6 +39,20 @@
{% block right_panel %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
{% if message.tags == 'info' %}
<li class="notification">共享给 {{ message }} 成功,请前往<a href="{{ SITE_ROOT }}shareadmin/">共享管理</a>查看。</li>
{% endif %}
{% if message.tags == 'error' %}
<li class="error">共享给 {{ message }} 失败</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if output_msg %}
{% for key, value in output_msg.items %}
{% if key == 'info_msg' %}
@ -101,10 +115,10 @@
<p>暂无</p>
{% endif %}
<form id="repo-share-form" action="{{ SITE_ROOT }}home/my/" method="post" name="repo-share-form" class="hide">
<form id="repo-share-form" action="{{ SITE_ROOT }}share/add/" method="post" name="repo-share-form" class="hide">
<label>邮箱或小组:</label><br />
<textarea id="to_email" name="to_email"></textarea><br />
<input id="share_repo_id" type="hidden" name="share_repo_id" value="" />
<textarea id="email_or_group" name="email_or_group"></textarea><br />
<input id="repo_id" type="hidden" name="repo_id" value="" />
<p class="error hide">输入不能为空。</p>
<input type="submit" value="提交" id="share-submit-btn" />
</form>
@ -125,14 +139,14 @@ $(function() {
{% endfor %}
$(".repo-share-btn").click(function() {
$("#share_repo_id").val($(this).attr("data"));
$("#repo_id").val($(this).attr("data"));
$("#repo-share-form").modal({appendTo: "#main"});
addAutocomplete('#to_email', '#repo-share-form', share_list);
addAutocomplete('#email_or_group', '#repo-share-form', share_list);
});
//check before post
$('#share-submit-btn').click(function() {
if (!$.trim($('#to_email').attr('value'))) {
if (!$.trim($('#email_or_group').attr('value'))) {
$('#repo-share-form .error').removeClass('hide');
return false;
}

View File

@ -34,4 +34,3 @@
-->
</ul>
{% endblock %}

View File

@ -1,93 +1,103 @@
{% extends "myhome_base.html" %}
{% load seahub_tags %}
{% block info_bar_message %}
{% if request.user.is_authenticated %}
{{ block.super }}
{% else %}
<div id="info-bar">
<span class="info">当前链接会在短期内失效,欢迎您 <a href="http://seafile.com/" target="_blank">加入Seafile </a>体验更多功能。</span>
</div>
{% endif %}
{% endblock %}
{% block main_panel %}
<h2 class="subject">
{{repo.props.name}}
</h2>
<div class="side fright">
<h3>基本信息</h3>
<p>{{repo.props.desc}}</p>
<p>大小:{{ repo_size|filesizeformat }}</p>
{% if not repo.props.encrypted or password_set %}
{% if is_owner or repo_ap == 'public' or share_to_me %}
<div class="latest-commit">
<h3>基本信息</h3>
<p>{{repo.props.desc}}</p>
<p>大小:{{ repo_size|filesizeformat }}</p>
{% if not repo.props.encrypted or password_set %}
{% if can_access %}
<div class="latest-commit">
<h3>最新修改<a href="{{ SITE_ROOT }}repo/history/{{repo.props.id}}/" class="more">(更多)</a></h3>
<p>{{ latest_commit.props.desc|translate_commit_desc }}</p>
<p class="al-rt">
<span class="author">by
{% if latest_commit.props.creator_name %}
{{ latest_commit.props.creator_name }}
{% else %}
未知
{% endif %}
</span>
<span class="time">{{ latest_commit.props.ctime|translate_commit_time }}</span>
<span class="author">by
{% if latest_commit.props.creator_name %}
{{ latest_commit.props.creator_name }}
{% else %}
未知
{% endif %}
</span>
<span class="time">{{ latest_commit.props.ctime|translate_commit_time }}</span>
</p>
</div>
{% endif %}
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
<div class="main fleft">
{% if repo.props.encrypted and not password_set %}
<p class="access-notice">该目录已加密。如需在线查看里面的内容请输入解密密码。密码只会在服务器上暂存1小时。</p>
<form action="{{ SITE_ROOT }}repo/{{ repo.id }}/" method="post">
<label>密码:</label>
<input id="id_password" type="password" name="password" maxlength="64" /><br />
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<input type="submit" value="提交" />
</form>
{% else %}
{% if not is_owner and repo_ap == 'own' and not share_to_me %}
<p class="access-notice">该同步目录web匿名访问未开启不能在线查看。</p>
{% else %}
<p class="path">
当前路径:
{% for name, link in zipped %}
{% if not forloop.last %}
<a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ link|urlencode }}">{{ name }}</a> /
{% else %}
{{ name }}
{% endif %}
{% endfor %}
</p>
<table>
<tr>
<th width="5%"></th>
<th width="69%">名字</th>
<th width="13%">大小</th>
<th width="13%">操作</th>
</tr>
{% for dirent in dir_list %}
<tr>
<td class="icon-container"><img src="{{ MEDIA_URL }}img/folder-icon-24.png" alt="目录" /></td>
<td><a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.obj_name }}</a></td>
<td></td>
<td></td>
</tr>
{% endfor %}
{% for dirent in file_list %}
<tr>
<td class="icon-container"><img src="{{ MEDIA_URL }}img/{{ dirent.obj_name|file_icon_filter }}" alt="文件" /></td>
<td>{{ dirent.props.obj_name }}</td>
<td>{{ dirent.file_size|filesizeformat }}</td>
<td>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=view">查看</a>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=download">下载</a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if repo.props.encrypted and not password_set %}
<p class="access-notice">该目录已加密。如需在线查看里面的内容请输入解密密码。密码只会在服务器上暂存1小时。</p>
<form action="{{ SITE_ROOT }}repo/{{ repo.id }}/" method="post">
<label>密码:</label>
<input id="id_password" type="password" name="password" maxlength="64" /><br />
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<input type="submit" value="提交" />
</form>
{% else %}
{% if not can_access %}
<p class="access-notice">无法在线查看该同步目录。</p>
{% else %}
<p class="path">
当前路径:
{% for name, link in zipped %}
{% if not forloop.last %}
<a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ link|urlencode }}">{{ name }}</a> /
{% else %}
{{ name }}
{% endif %}
{% endfor %}
</p>
<table>
<tr>
<th width="5%"></th>
<th width="69%">名字</th>
<th width="13%">大小</th>
<th width="13%">操作</th>
</tr>
{% for dirent in dir_list %}
<tr>
<td class="icon-container"><img src="{{ MEDIA_URL }}img/folder-icon-24.png" alt="目录" /></td>
<td><a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.obj_name }}</a></td>
<td></td>
<td></td>
</tr>
{% endfor %}
{% for dirent in file_list %}
<tr>
<td class="icon-container"><img src="{{ MEDIA_URL }}img/{{ dirent.obj_name|file_icon_filter }}" alt="文件" /></td>
<td>{{ dirent.props.obj_name }}</td>
<td>{{ dirent.file_size|filesizeformat }}</td>
<td>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=view">查看</a>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=download">下载</a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}
</div>
{% endblock %}

View File

@ -8,7 +8,7 @@
</h2>
<div class="side fright">
{% if is_owner or repo_ap == 'public' or share_to_me %}
{% if can_access %}
<div class="latest-commit">
<h3>修改信息</h3>
<p>{{ current_commit.props.desc|translate_commit_desc }}</p>
@ -24,6 +24,7 @@
</p>
</div>
{% endif %}
<h3>操作</h3>
<ul class="with-bg">
<li><a href="{{ SITE_ROOT }}repo/history/{{ repo.id }}/">返回历史列表</a></li>
@ -31,8 +32,8 @@
</div>
<div class="main fleft">
{% if not is_owner and repo_ap == 'own' and not share_to_me %}
<p class="access-notice">该同步目录web匿名访问未开启不能在线查看</p>
{% if not can_access %}
<p class="access-notice">无法在线查看该同步目录。</p>
{% else %}
<p class="path">当前路径:
{% for name, link in zipped %}

10
urls.py
View File

@ -7,9 +7,10 @@ from seahub.views import root, peers, myhome, \
activate_user, user_add, user_remove, \
ownerhome, repo_history_dir, repo_history_revert, \
user_info, repo_set_access_property, repo_access_file, \
repo_add_share, repo_list_share, repo_remove_share, repo_download, \
repo_remove_share, repo_download, \
seafile_access_check, back_local, group_admin, repo_history_changes
from seahub.notifications.views import notification_list
from seahub.share.views import share_admin
# Uncomment the next two lines to enable the admin:
#from django.contrib import admin
@ -32,9 +33,9 @@ urlpatterns = patterns('',
#url(r'^home/$', direct_to_template, { 'template': 'home.html' } ),
url(r'^home/my/$', myhome, name='myhome'),
url(r'^home/owner/(?P<owner_name>[^/]+)/$', ownerhome, name='ownerhome'),
url(r'^shareadmin/$', repo_list_share, name='repo_list_share'),
url(r'^shareadmin/addshare/$', repo_add_share, name='repo_add_share'),
(r'^share/', include('share.urls')),
url(r'^shareadmin/$', share_admin, name='share_admin'),
(r'^shareadmin/removeshare/$', repo_remove_share),
url(r'^repo/(?P<repo_id>[^/]+)/$', repo, name='repo'),
@ -66,7 +67,6 @@ urlpatterns = patterns('',
(r'^group/', include('seahub.group.urls')),
url(r'^groupadmin/$', group_admin, name='group_admin'),
(r'^profile/', include('seahub.profile.urls')),
(r'^share/', include('share.urls')),
(r'^back/local/$', back_local),
)

223
views.py
View File

@ -4,6 +4,7 @@ import stat
import simplejson as json
from urllib import quote
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import IntegrityError
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response, redirect
@ -22,8 +23,6 @@ from pysearpc import SearpcError
from seahub.base.accounts import CcnetUser
from seahub.contacts.models import Contact
from seahub.share.forms import GroupAddRepoForm
from seahub.share.models import GroupShare, UserShare
from forms import AddUserForm
from utils import go_permission_error, go_error, list_to_string, \
get_httpserver_root, get_ccnetapplet_root, gen_token
@ -54,29 +53,11 @@ def peers(request):
}, context_instance=RequestContext(request))
def validate_owner(request, repo_id):
# check whether email in the request own the repo
return seafserv_threaded_rpc.is_repo_owner(request.user.username, repo_id)
def check_shared_repo(request, repo_id):
"""
check whether user has been shared this repo or
the repo share to the groups user join
Check whether email in the request own the repo
"""
repos = seafserv_threaded_rpc.list_share_repos(request.user.username, 'to_email', -1, -1)
for repo in repos:
if repo.props.id == repo_id:
return True
groups = ccnet_rpc.get_groups(request.user.username)
# for every group that user joined...
for group in groups:
# ...get repo ids in that group, and check whether repo ids contains that repo id
repo_ids = get_group_repoids(group.props.id)
if repo_ids.__contains__(repo_id):
return True
return False
return seafserv_threaded_rpc.is_repo_owner(request.user.username, repo_id)
def validate_emailuser(emailuser):
"""
@ -93,14 +74,41 @@ def validate_emailuser(emailuser):
else:
return False
def check_shared_repo(request, repo_id):
"""
Check whether user has been shared this repo or
the repo share to the groups user join or
got token if user is not logged in
"""
if not request.user.is_authenticated():
token = request.COOKIES.get('anontoken', None)
if token:
return True
else:
return False
repos = seafserv_threaded_rpc.list_share_repos(request.user.username, 'to_email', -1, -1)
for repo in repos:
if repo.props.id == repo_id:
return True
groups = ccnet_rpc.get_groups(request.user.username)
# for every group that user joined...
for group in groups:
# ...get repo ids in that group, and check whether repo ids contains that repo id
repo_ids = get_group_repoids(group.props.id)
if repo_id in repo_ids:
return True
return False
def access_to_repo(request, repo_id, repo_ap):
"""
Check whether user in the request can access to repo, which means user can
view directory entries on repo page, and repo_history_dir page.
"""
# if repo is 'own' and user is not staff and is not owner
# and not shared this repo, then goto 404 page..
if repo_ap == 'own' and not validate_owner(request, repo_id) \
and not check_shared_repo(request, repo_id) and not request.user.is_staff:
return False
@ -133,16 +141,26 @@ def render_repo(request, repo_id, error=''):
# get repo web access property, if no repo access property in db, then
# assume repo ap is 'own'
repo_ap = seafserv_threaded_rpc.repo_query_access_property(repo_id)
if repo_ap == None:
if not repo_ap:
repo_ap = 'own'
if not access_to_repo(request, repo_id, repo_ap):
return go_permission_error(request, u'该同步目录未公开')
# check whether user can view repo
if access_to_repo(request, repo_id, repo_ap):
can_access = True
else:
can_access = False
# check whether use is repo owner
if validate_owner(request, repo_id):
is_owner = True
else:
is_owner = False
repo = get_repo(repo_id)
if not repo:
return go_error(request, u'该同步目录不存在')
# query whether set password if repo is encrypted
password_set = False
if repo.props.encrypted:
try:
@ -152,9 +170,11 @@ def render_repo(request, repo_id, error=''):
except SearpcError, e:
return go_error(request, e.msg)
# query repo infomation
repo_size = seafserv_threaded_rpc.server_repo_size(repo_id)
latest_commit = get_commits(repo_id, 0, 1)[0]
# get repo dirents
dirs = []
path = ''
zipped = []
@ -190,22 +210,9 @@ def render_repo(request, repo_id, error=''):
# generate path and link
zipped = gen_path_link(path, repo.name)
# check whether use is repo owner
is_owner = False
if request.user.is_authenticated():
if validate_owner(request, repo_id):
is_owner = True
# used to determin whether show repo content in repo.html
# if a repo is shared to me, or repo shared to the group I joined,
# then I can view repo content on the web
if check_shared_repo(request, repo_id):
share_to_me = True
else:
share_to_me = False
return render_to_response('repo.html', {
"repo": repo,
"can_access": can_access,
"latest_commit": latest_commit,
"is_owner": is_owner,
"password_set": password_set,
@ -213,7 +220,6 @@ def render_repo(request, repo_id, error=''):
"repo_size": repo_size,
"dir_list": dir_list,
"file_list": file_list,
"share_to_me": share_to_me,
"path" : path,
"zipped" : zipped,
"error" : error,
@ -307,14 +313,24 @@ def repo_history_dir(request, repo_id):
repo_ap = seafserv_threaded_rpc.repo_query_access_property(repo_id)
if not repo_ap:
repo_ap = 'own'
if not access_to_repo(request, repo_id, repo_ap):
raise Http404
# check whether user can view repo
if access_to_repo(request, repo_id, repo_ap):
can_access = True
else:
can_access = False
# check whether use is repo owner
if validate_owner(request, repo_id):
is_owner = True
else:
is_owner = False
repo = get_repo(repo_id)
if not repo:
raise Http404
# query whether set password if repo is encrypted
password_set = False
if repo.props.encrypted:
try:
@ -334,11 +350,7 @@ def repo_history_dir(request, repo_id):
if not current_commit:
raise Http404
is_owner = False
if request.user.is_authenticated():
if validate_owner(request, repo_id):
is_owner = True
# get repo dirents
dirs = []
path = ''
zipped = []
@ -367,22 +379,14 @@ def repo_history_dir(request, repo_id):
# generate path and link
zipped = gen_path_link(path, repo.name)
# used to determin whether show repo content in repo.html
# if a repo is shared to me, or repo shared to the group I joined,
# then I can view repo content on the web
if check_shared_repo(request, repo_id):
share_to_me = True
else:
share_to_me = False
return render_to_response('repo_history_dir.html', {
"repo": repo,
"can_access": can_access,
"current_commit": current_commit,
"is_owner": is_owner,
"repo_ap": repo_ap,
"dir_list": dir_list,
"file_list": file_list,
"share_to_me": share_to_me,
"path" : path,
"zipped" : zipped,
}, context_instance=RequestContext(request))
@ -554,10 +558,6 @@ def myhome(request):
in_repos = seafserv_threaded_rpc.list_share_repos(request.user.username,
'to_email', -1, -1)
# handle share repo request
if request.method == 'POST':
output_msg = repo_add_share(request)
# my contacts
contacts = Contact.objects.filter(user_email=email)
@ -570,12 +570,11 @@ def myhome(request):
groups_manage.append(group)
else:
groups_join.append(group)
return render_to_response('myhome.html', {
"owned_repos": owned_repos,
"quota_usage": quota_usage,
"in_repos": in_repos,
"output_msg": output_msg,
"contacts": contacts,
"groups": groups,
"groups_manage": groups_manage,
@ -658,96 +657,6 @@ def repo_access_file(request, repo_id, obj_id):
token,
request.user.username)
return HttpResponseRedirect(redirect_url)
@login_required
def repo_add_share(request):
output_msg = {}
if request.method == 'POST':
from_email = request.user.username
repo_id = request.POST.get('share_repo_id', '')
# Handle the diffent separator
to_email_str = request.POST.get('to_email', '').replace(';',',')
to_email_str = to_email_str.replace('\n',',')
to_email_str = to_email_str.replace('\r',',')
to_email_list = to_email_str.split(',')
info_emails = []
err_emails = []
for to_email in to_email_list:
to_email = to_email.strip(' ')
if not to_email:
continue
# if to_email is user name, the format is: 'example@mail.com';
# if to_email is group, the format is 'group_name <creator@mail.com>'
if (to_email.split(' ')[0].find('@') == -1):
group_name = to_email.split(' ')[0]
group_creator = to_email.split(' ')[1]
if validate_owner(request, repo_id):
# get all the groups the user joined
groups = ccnet_rpc.get_groups(request.user.username)
find = False
for group in groups:
# for every group that user joined, if group name and
# group creator matchs, then has find the group
if group.props.group_name == group_name and \
group_creator.find(group.props.creator_name) >= 0:
from seahub.group.views import group_share_repo
group_share_repo(request, repo_id, int(group.props.id), from_email)
find = True
info_emails.append(group_name)
if not find:
err_emails.append(group_name)
else:
err_emails.append(group_name)
else:
if validate_emailuser(to_email) and validate_owner(request, repo_id):
try:
seafserv_threaded_rpc.add_share(repo_id, from_email, to_email, 'rw')
info_emails.append(to_email)
except SearpcError, e:
err_emails.append(to_email)
else:
err_emails.append(to_email)
if info_emails:
output_msg['info_msg'] = u'共享给%s成功,' % list_to_string(info_emails)
if err_emails:
output_msg['err_msg'] = u'共享给%s失败' % list_to_string(err_emails)
return output_msg
@login_required
def repo_list_share(request):
username = request.user.username
# repos that are share to user
out_repos = seafserv_threaded_rpc.list_share_repos(username, 'from_email', -1, -1)
# repos that are share to groups
group_repos = seafserv_threaded_rpc.get_group_my_share_repos(request.user.username)
for group_repo in group_repos:
repo_id = group_repo.props.repo_id
if not repo_id:
continue
repo = get_repo(repo_id)
if not repo:
continue
group_id = group_repo.props.group_id
group = ccnet_rpc.get_group(int(group_id))
if not group:
continue
repo.props.shared_email = group.props.group_name
repo.gid = group_id
out_repos.append(repo)
return render_to_response('share_repos.html', {
"out_repos": out_repos,
}, context_instance=RequestContext(request))
@login_required
def repo_download(request):
@ -816,7 +725,7 @@ def repo_remove_share(request):
referer = request.META.get('HTTP_REFERER', None)
if not referer:
referer = 'repo_list_share'
referer = 'share_admin'
return HttpResponseRedirect(reverse(referer))
else:
return HttpResponseRedirect(referer)