pull/1075/head
Piero Toffanin 2021-10-18 16:42:13 -04:00
rodzic 2beaad3910
commit 0db85bba66
5 zmienionych plików z 92 dodań i 13 usunięć

Wyświetl plik

@ -70,9 +70,9 @@ class ProjectViewSet(viewsets.ModelViewSet):
result = [] result = []
perms = get_users_with_perms(project, attach_perms=True) perms = get_users_with_perms(project, attach_perms=True, with_group_users=False)
for user in perms: for user in perms:
result.append({'user': user.username, result.append({'username': user.username,
'owner': project.owner == user, 'owner': project.owner == user,
'permissions': normalized_perm_names(perms[user])}) 'permissions': normalized_perm_names(perms[user])})

Wyświetl plik

@ -12,12 +12,16 @@ from rest_framework_jwt.views import obtain_jwt_token
from .tiler import TileJson, Bounds, Metadata, Tiles, Export from .tiler import TileJson, Bounds, Metadata, Tiles, Export
from .potree import Scene, CameraView from .potree import Scene, CameraView
from .workers import CheckTask, GetTaskResult from .workers import CheckTask, GetTaskResult
from .users import UserViewSet
from webodm import settings
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'projects', ProjectViewSet) router.register(r'projects', ProjectViewSet)
router.register(r'processingnodes', ProcessingNodeViewSet) router.register(r'processingnodes', ProcessingNodeViewSet)
router.register(r'presets', PresetViewSet, base_name='presets') router.register(r'presets', PresetViewSet, base_name='presets')
if settings.ENABLE_USERS_API:
router.register(r'users', UserViewSet, base_name='users')
tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project') tasks_router = routers.NestedSimpleRouter(router, r'projects', lookup='project')
tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks') tasks_router.register(r'tasks', TaskViewSet, base_name='projects-tasks')

33
app/api/users.py 100644
Wyświetl plik

@ -0,0 +1,33 @@
from django.contrib.auth.models import User
from rest_framework import serializers, viewsets, mixins, status, exceptions, permissions
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email']
class UserViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
# Disable pagination when not requesting any page
def paginate_queryset(self, queryset):
if self.paginator and self.request.query_params.get(self.paginator.page_query_param, None) is None:
return None
return super().paginate_queryset(queryset)
def get_queryset(self):
queryset = User.objects.all()
search = self.request.query_params.get('search', None)
if search is not None:
queryset = queryset.filter(username__istartswith=search) | queryset.filter(email__istartswith=search)
limit = self.request.query_params.get('limit', None)
if limit is not None:
try:
queryset = queryset[:abs(int(limit))]
except ValueError:
raise exceptions.ValidationError(detail="Invalid query parameters")
return queryset

Wyświetl plik

@ -25,7 +25,8 @@ class EditPermissionsPanel extends React.Component {
error: "", error: "",
loading: false, loading: false,
permissions: [], permissions: [],
validUsernames: [] validUsernames: {},
validatingUser: false
}; };
this.backgroundFailedColor = Css.getValue('btn-danger', 'backgroundColor'); this.backgroundFailedColor = Css.getValue('btn-danger', 'backgroundColor');
@ -36,7 +37,8 @@ class EditPermissionsPanel extends React.Component {
this.permsRequest = this.permsRequest =
$.getJSON(`/api/projects/${this.props.projectId}/permissions/`, json => { $.getJSON(`/api/projects/${this.props.projectId}/permissions/`, json => {
let validUsernames = json.map(p => p.user); let validUsernames = {};
json.forEach(p => validUsernames[p.username] = true);
this.setState({validUsernames, permissions: json}); this.setState({validUsernames, permissions: json});
}) })
.fail(() => { .fail(() => {
@ -47,12 +49,43 @@ class EditPermissionsPanel extends React.Component {
}); });
} }
validateUsername = (username) => {
if (this.validateReq){
this.validateReq.abort();
this.validateReq = null;
}
if (this.validateTimeout){
clearTimeout(this.validateTimeout);
this.validateTimeout = null;
}
this.setState({validatingUser: true});
this.validateTimeout = setTimeout(() => {
this.validateReq = $.getJSON(`/api/users/?limit=30&search=${encodeURIComponent(username)}`)
.done((json) => {
json.forEach(u => {
this.state.validUsernames[u.username] = true;
});
this.setState({validUsernames: this.state.validUsernames});
}).fail(() => {
}).always(() => {
this.setState({validatingUser: false});
});
}, 300);
}
componentDidMount(){ componentDidMount(){
if (!this.props.lazyLoad) this.loadPermissions(); if (!this.props.lazyLoad) this.loadPermissions();
} }
componentWillUnmount(){ componentWillUnmount(){
if (this.permsRequest) this.permsRequest.abort(); if (this.permsRequest) this.permsRequest.abort();
if (this.validateReq) this.validateReq.abort();
if (this.validateTimeout) clearTimeout(this.validateTimeout);
} }
handleChangePermissionRole = e => { handleChangePermissionRole = e => {
@ -61,7 +94,9 @@ class EditPermissionsPanel extends React.Component {
handleChangePermissionUser = perm => { handleChangePermissionUser = perm => {
return e => { return e => {
perm.user = e.target.value; perm.username = e.target.value;
this.validateUsername(perm.username);
// Update // Update
this.setState({permissions: this.state.permissions}); this.setState({permissions: this.state.permissions});
@ -87,13 +122,13 @@ class EditPermissionsPanel extends React.Component {
} }
getColorFor = (username) => { getColorFor = (username) => {
if (this.state.validUsernames.indexOf(username) !== -1) return ""; if (this.state.validatingUser || this.state.validUsernames[username]) return "";
else return this.backgroundFailedColor; else return this.backgroundFailedColor;
} }
addNewPermission = () => { addNewPermission = () => {
this.setState(update(this.state, { this.setState(update(this.state, {
permissions: {$push: [{user: "", permissions: ["view"]}]} permissions: {$push: [{username: "", permissions: ["view"]}]}
})); }));
setTimeout(() => { setTimeout(() => {
@ -110,18 +145,19 @@ class EditPermissionsPanel extends React.Component {
} }
render() { render() {
const permissions = this.state.permissions.map((p, i) => <div key={i}> const permissions = this.state.permissions.map((p, i) => <form autoComplete="off" key={i}>
<div className="permission"> <div className="permission">
<div className="username-container"> <div className="username-container">
<i className="fa fa-user user-indicator"/> <i className="fa fa-user user-indicator"/>
<input <input
style={{color: this.getColorFor(p.user)}} style={{color: this.getColorFor(p.username)}}
onChange={this.handleChangePermissionUser(p)} onChange={this.handleChangePermissionUser(p)}
type="text" type="text"
autoComplete="off"
disabled={p.owner} disabled={p.owner}
value={p.user} value={p.username}
className="form-control username" className="form-control username"
placeholder={_("Username / e-mail")} placeholder={_("Username")}
ref={(domNode) => this.lastTextbox = domNode} /> ref={(domNode) => this.lastTextbox = domNode} />
</div> </div>
<div className="remove"> <div className="remove">
@ -133,7 +169,7 @@ class EditPermissionsPanel extends React.Component {
</select> </select>
</div> </div>
</div> </div>
</div>); </form>);
return ( return (
<div className="edit-permissions-panel"> <div className="edit-permissions-panel">

Wyświetl plik

@ -69,6 +69,12 @@ SINGLE_USER_MODE = False
# URL to redirect to if there are no processing nodes when visiting the dashboard # URL to redirect to if there are no processing nodes when visiting the dashboard
PROCESSING_NODES_ONBOARDING = None PROCESSING_NODES_ONBOARDING = None
# Enable the /api/users endpoint which is used for autocompleting
# usernames when handling project permissions. This can be disabled
# for security reasons if you don't want to let authenticated users
# retrieve the user list.
ENABLE_USERS_API = True
# Enable desktop mode. In desktop mode some styling changes # Enable desktop mode. In desktop mode some styling changes
# are applied to make the application look nicer on desktop # are applied to make the application look nicer on desktop
# as well as disabling certain features (e.g. sharing) # as well as disabling certain features (e.g. sharing)