api.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import uuid
  17. from django.apps import apps
  18. from django.db.models import Q, F
  19. from django.utils.translation import ugettext as _
  20. from django.core.validators import validate_email
  21. from django.core.exceptions import ValidationError
  22. from django.conf import settings
  23. from taiga.base import exceptions as exc
  24. from taiga.base import filters
  25. from taiga.base import response
  26. from taiga.auth.tokens import get_user_for_token
  27. from taiga.base.decorators import list_route
  28. from taiga.base.decorators import detail_route
  29. from taiga.base.api import ModelCrudViewSet
  30. from taiga.base.filters import PermissionBasedFilterBackend
  31. from taiga.base.api.utils import get_object_or_404
  32. from taiga.base.filters import MembersFilterBackend
  33. from taiga.projects.votes import services as votes_service
  34. from taiga.projects.serializers import StarredSerializer
  35. from easy_thumbnails.source_generators import pil_image
  36. from djmail.template_mail import MagicMailBuilder
  37. from djmail.template_mail import InlineCSSTemplateMail
  38. from . import models
  39. from . import serializers
  40. from . import permissions
  41. from . import filters as user_filters
  42. from . import services
  43. from .signals import user_cancel_account as user_cancel_account_signal
  44. class UsersViewSet(ModelCrudViewSet):
  45. permission_classes = (permissions.UserPermission,)
  46. admin_serializer_class = serializers.UserAdminSerializer
  47. serializer_class = serializers.UserSerializer
  48. queryset = models.User.objects.all().prefetch_related("memberships")
  49. filter_backends = (MembersFilterBackend,)
  50. def get_serializer_class(self):
  51. if self.action in ["partial_update", "update", "retrieve", "by_username"]:
  52. user = self.object
  53. if self.request.user == user:
  54. return self.admin_serializer_class
  55. return self.serializer_class
  56. def create(self, *args, **kwargs):
  57. raise exc.NotSupported()
  58. def list(self, request, *args, **kwargs):
  59. self.object_list = MembersFilterBackend().filter_queryset(request,
  60. self.get_queryset(),
  61. self)
  62. page = self.paginate_queryset(self.object_list)
  63. if page is not None:
  64. serializer = self.get_pagination_serializer(page)
  65. else:
  66. serializer = self.get_serializer(self.object_list, many=True)
  67. return response.Ok(serializer.data)
  68. @list_route(methods=["GET"])
  69. def by_username(self, request, *args, **kwargs):
  70. username = request.QUERY_PARAMS.get("username", None)
  71. return self.retrieve(request, username=username)
  72. def retrieve(self, request, *args, **kwargs):
  73. self.object = get_object_or_404(models.User, **kwargs)
  74. self.check_permissions(request, 'retrieve', self.object)
  75. serializer = self.get_serializer(self.object)
  76. return response.Ok(serializer.data)
  77. @detail_route(methods=["GET"])
  78. def contacts(self, request, *args, **kwargs):
  79. user = get_object_or_404(models.User, **kwargs)
  80. self.check_permissions(request, 'contacts', user)
  81. self.object_list = user_filters.ContactsFilterBackend().filter_queryset(
  82. user, request, self.get_queryset(), self).extra(
  83. select={"complete_user_name":"concat(full_name, username)"}).order_by("complete_user_name")
  84. page = self.paginate_queryset(self.object_list)
  85. if page is not None:
  86. serializer = self.serializer_class(page.object_list, many=True)
  87. else:
  88. serializer = self.serializer_class(self.object_list, many=True)
  89. return response.Ok(serializer.data)
  90. @detail_route(methods=["GET"])
  91. def stats(self, request, *args, **kwargs):
  92. user = get_object_or_404(models.User, **kwargs)
  93. self.check_permissions(request, "stats", user)
  94. return response.Ok(services.get_stats_for_user(user, request.user))
  95. @detail_route(methods=["GET"])
  96. def favourites(self, request, *args, **kwargs):
  97. for_user = get_object_or_404(models.User, **kwargs)
  98. from_user = request.user
  99. self.check_permissions(request, 'favourites', for_user)
  100. filters = {
  101. "type": request.GET.get("type", None),
  102. "action": request.GET.get("action", None),
  103. "q": request.GET.get("q", None),
  104. }
  105. self.object_list = services.get_favourites_list(for_user, from_user, **filters)
  106. page = self.paginate_queryset(self.object_list)
  107. extra_args = {
  108. "many": True,
  109. "user_votes": services.get_voted_content_for_user(request.user),
  110. "user_watching": services.get_watched_content_for_user(request.user),
  111. }
  112. if page is not None:
  113. serializer = serializers.FavouriteSerializer(page.object_list, **extra_args)
  114. else:
  115. serializer = serializers.FavouriteSerializer(self.object_list, **extra_args)
  116. return response.Ok(serializer.data)
  117. @list_route(methods=["POST"])
  118. def password_recovery(self, request, pk=None):
  119. username_or_email = request.DATA.get('username', None)
  120. self.check_permissions(request, "password_recovery", None)
  121. if not username_or_email:
  122. raise exc.WrongArguments(_("Invalid username or email"))
  123. try:
  124. queryset = models.User.objects.all()
  125. user = queryset.get(Q(username=username_or_email) |
  126. Q(email=username_or_email))
  127. except models.User.DoesNotExist:
  128. raise exc.WrongArguments(_("Invalid username or email"))
  129. user.token = str(uuid.uuid1())
  130. user.save(update_fields=["token"])
  131. mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail)
  132. email = mbuilder.password_recovery(user, {"user": user})
  133. email.send()
  134. return response.Ok({"detail": _("Mail sended successful!")})
  135. @list_route(methods=["POST"])
  136. def change_password_from_recovery(self, request, pk=None):
  137. """
  138. Change password with token (from password recovery step).
  139. """
  140. self.check_permissions(request, "change_password_from_recovery", None)
  141. serializer = serializers.RecoverySerializer(data=request.DATA, many=False)
  142. if not serializer.is_valid():
  143. raise exc.WrongArguments(_("Token is invalid"))
  144. try:
  145. user = models.User.objects.get(token=serializer.data["token"])
  146. except models.User.DoesNotExist:
  147. raise exc.WrongArguments(_("Token is invalid"))
  148. user.set_password(serializer.data["password"])
  149. user.token = None
  150. user.save(update_fields=["password", "token"])
  151. return response.NoContent()
  152. @list_route(methods=["POST"])
  153. def change_password(self, request, pk=None):
  154. """
  155. Change password to current logged user.
  156. """
  157. self.check_permissions(request, "change_password", None)
  158. current_password = request.DATA.get("current_password")
  159. password = request.DATA.get("password")
  160. # NOTE: GitHub users have no password yet (request.user.passwor == '') so
  161. # current_password can be None
  162. if not current_password and request.user.password:
  163. raise exc.WrongArguments(_("Current password parameter needed"))
  164. if not password:
  165. raise exc.WrongArguments(_("New password parameter needed"))
  166. if len(password) < 6:
  167. raise exc.WrongArguments(_("Invalid password length at least 6 charaters needed"))
  168. if current_password and not request.user.check_password(current_password):
  169. raise exc.WrongArguments(_("Invalid current password"))
  170. request.user.set_password(password)
  171. request.user.save(update_fields=["password"])
  172. return response.NoContent()
  173. @list_route(methods=["POST"])
  174. def change_avatar(self, request):
  175. """
  176. Change avatar to current logged user.
  177. """
  178. self.check_permissions(request, "change_avatar", None)
  179. avatar = request.FILES.get('avatar', None)
  180. if not avatar:
  181. raise exc.WrongArguments(_("Incomplete arguments"))
  182. try:
  183. pil_image(avatar)
  184. except Exception:
  185. raise exc.WrongArguments(_("Invalid image format"))
  186. request.user.photo = avatar
  187. request.user.save(update_fields=["photo"])
  188. user_data = self.admin_serializer_class(request.user).data
  189. return response.Ok(user_data)
  190. @list_route(methods=["POST"])
  191. def remove_avatar(self, request):
  192. """
  193. Remove the avatar of current logged user.
  194. """
  195. self.check_permissions(request, "remove_avatar", None)
  196. request.user.photo = None
  197. request.user.save(update_fields=["photo"])
  198. user_data = self.admin_serializer_class(request.user).data
  199. return response.Ok(user_data)
  200. #TODO: commit_on_success
  201. def partial_update(self, request, *args, **kwargs):
  202. """
  203. We must detect if the user is trying to change his email so we can
  204. save that value and generate a token that allows him to validate it in
  205. the new email account
  206. """
  207. user = self.get_object()
  208. self.check_permissions(request, "update", user)
  209. ret = super(UsersViewSet, self).partial_update(request, *args, **kwargs)
  210. new_email = request.DATA.get('email', None)
  211. if new_email is not None:
  212. valid_new_email = True
  213. duplicated_email = models.User.objects.filter(email = new_email).exists()
  214. try:
  215. validate_email(new_email)
  216. except ValidationError:
  217. valid_new_email = False
  218. valid_new_email = valid_new_email and new_email != request.user.email
  219. if duplicated_email:
  220. raise exc.WrongArguments(_("Duplicated email"))
  221. elif not valid_new_email:
  222. raise exc.WrongArguments(_("Not valid email"))
  223. #We need to generate a token for the email
  224. request.user.email_token = str(uuid.uuid1())
  225. request.user.new_email = new_email
  226. request.user.save(update_fields=["email_token", "new_email"])
  227. mbuilder = MagicMailBuilder(template_mail_cls=InlineCSSTemplateMail)
  228. email = mbuilder.change_email(request.user.new_email, {"user": request.user,
  229. "lang": request.user.lang})
  230. email.send()
  231. return ret
  232. @list_route(methods=["POST"])
  233. def change_email(self, request, pk=None):
  234. """
  235. Verify the email change to current logged user.
  236. """
  237. serializer = serializers.ChangeEmailSerializer(data=request.DATA, many=False)
  238. if not serializer.is_valid():
  239. raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
  240. "didn't use it before?"))
  241. try:
  242. user = models.User.objects.get(email_token=serializer.data["email_token"])
  243. except models.User.DoesNotExist:
  244. raise exc.WrongArguments(_("Invalid, are you sure the token is correct and you "
  245. "didn't use it before?"))
  246. self.check_permissions(request, "change_email", user)
  247. user.email = user.new_email
  248. user.new_email = None
  249. user.email_token = None
  250. user.save(update_fields=["email", "new_email", "email_token"])
  251. return response.NoContent()
  252. @list_route(methods=["GET"])
  253. def me(self, request, pk=None):
  254. """
  255. Get me.
  256. """
  257. self.check_permissions(request, "me", None)
  258. user_data = self.admin_serializer_class(request.user).data
  259. return response.Ok(user_data)
  260. @list_route(methods=["POST"])
  261. def cancel(self, request, pk=None):
  262. """
  263. Cancel an account via token
  264. """
  265. serializer = serializers.CancelAccountSerializer(data=request.DATA, many=False)
  266. if not serializer.is_valid():
  267. raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
  268. try:
  269. max_age_cancel_account = getattr(settings, "MAX_AGE_CANCEL_ACCOUNT", None)
  270. user = get_user_for_token(serializer.data["cancel_token"], "cancel_account",
  271. max_age=max_age_cancel_account)
  272. except exc.NotAuthenticated:
  273. raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
  274. if not user.is_active:
  275. raise exc.WrongArguments(_("Invalid, are you sure the token is correct?"))
  276. user.cancel()
  277. return response.NoContent()
  278. def destroy(self, request, pk=None):
  279. user = self.get_object()
  280. self.check_permissions(request, "destroy", user)
  281. stream = request.stream
  282. request_data = stream is not None and stream.GET or None
  283. user_cancel_account_signal.send(sender=user.__class__, user=user, request_data=request_data)
  284. user.cancel()
  285. return response.NoContent()
  286. ######################################################
  287. ## Role
  288. ######################################################
  289. class RolesViewSet(ModelCrudViewSet):
  290. model = models.Role
  291. serializer_class = serializers.RoleSerializer
  292. permission_classes = (permissions.RolesPermission, )
  293. filter_backends = (filters.CanViewProjectFilterBackend,)
  294. filter_fields = ('project',)
  295. def pre_delete(self, obj):
  296. move_to = self.request.QUERY_PARAMS.get('moveTo', None)
  297. if move_to:
  298. membership_model = apps.get_model("projects", "Membership")
  299. role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
  300. qs = membership_model.objects.filter(project_id=obj.project.pk, role=obj)
  301. qs.update(role=role_dest)
  302. super().pre_delete(obj)