179 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
| # -*- coding: utf-8 -*-
 | |
| # Copyright 2014 matrix.org
 | |
| #
 | |
| # Licensed under the Apache License, Version 2.0 (the "License");
 | |
| # you may not use this file except in compliance with the License.
 | |
| # You may obtain a copy of the License at
 | |
| #
 | |
| #     http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| # Unless required by applicable law or agreed to in writing, software
 | |
| # distributed under the License is distributed on an "AS IS" BASIS,
 | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| # See the License for the specific language governing permissions and
 | |
| # limitations under the License.
 | |
| 
 | |
| """This module contains classes for authenticating the user."""
 | |
| 
 | |
| from twisted.internet import defer
 | |
| 
 | |
| from synapse.api.constants import Membership
 | |
| from synapse.api.errors import AuthError, StoreError, Codes
 | |
| from synapse.api.events.room import RoomMemberEvent
 | |
| 
 | |
| import logging
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class Auth(object):
 | |
| 
 | |
|     def __init__(self, hs):
 | |
|         self.hs = hs
 | |
|         self.store = hs.get_datastore()
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def check(self, event, snapshot, raises=False):
 | |
|         """ Checks if this event is correctly authed.
 | |
| 
 | |
|         Returns:
 | |
|             True if the auth checks pass.
 | |
|         Raises:
 | |
|             AuthError if there was a problem authorising this event. This will
 | |
|             be raised only if raises=True.
 | |
|         """
 | |
|         try:
 | |
|             if hasattr(event, "room_id"):
 | |
|                 if event.type == RoomMemberEvent.TYPE:
 | |
|                     allowed = yield self.is_membership_change_allowed(event)
 | |
|                     defer.returnValue(allowed)
 | |
|                 else:
 | |
|                     self._check_joined_room(
 | |
|                         member=snapshot.membership_state,
 | |
|                         user_id=snapshot.user_id,
 | |
|                         room_id=snapshot.room_id,
 | |
|                     )
 | |
|                     defer.returnValue(True)
 | |
|             else:
 | |
|                 raise AuthError(500, "Unknown event: %s" % event)
 | |
|         except AuthError as e:
 | |
|             logger.info("Event auth check failed on event %s with msg: %s",
 | |
|                         event, e.msg)
 | |
|             if raises:
 | |
|                 raise e
 | |
|         defer.returnValue(False)
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def check_joined_room(self, room_id, user_id):
 | |
|         try:
 | |
|             member = yield self.store.get_room_member(
 | |
|                 room_id=room_id,
 | |
|                 user_id=user_id
 | |
|             )
 | |
|             self._check_joined_room(member, user_id, room_id)
 | |
|             defer.returnValue(member)
 | |
|         except AttributeError:
 | |
|             pass
 | |
|         defer.returnValue(None)
 | |
| 
 | |
|     def _check_joined_room(self, member, user_id, room_id):
 | |
|         if not member or member.membership != Membership.JOIN:
 | |
|             raise AuthError(403, "User %s not in room %s (%s)" % (
 | |
|                 user_id, room_id, repr(member)
 | |
|             ))
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def is_membership_change_allowed(self, event):
 | |
|         target_user_id = event.state_key
 | |
| 
 | |
|         # does this room even exist
 | |
|         room = yield self.store.get_room(event.room_id)
 | |
|         if not room:
 | |
|             raise AuthError(403, "Room does not exist")
 | |
| 
 | |
|         # get info about the caller
 | |
|         try:
 | |
|             caller = yield self.store.get_room_member(
 | |
|                 user_id=event.user_id,
 | |
|                 room_id=event.room_id)
 | |
|         except:
 | |
|             caller = None
 | |
|         caller_in_room = caller and caller.membership == "join"
 | |
| 
 | |
|         # get info about the target
 | |
|         try:
 | |
|             target = yield self.store.get_room_member(
 | |
|                 user_id=target_user_id,
 | |
|                 room_id=event.room_id)
 | |
|         except:
 | |
|             target = None
 | |
|         target_in_room = target and target.membership == "join"
 | |
| 
 | |
|         membership = event.content["membership"]
 | |
| 
 | |
|         if Membership.INVITE == membership:
 | |
|             # Invites are valid iff caller is in the room and target isn't.
 | |
|             if not caller_in_room:  # caller isn't joined
 | |
|                 raise AuthError(403, "You are not in room %s." % event.room_id)
 | |
|             elif target_in_room:  # the target is already in the room.
 | |
|                 raise AuthError(403, "%s is already in the room." %
 | |
|                                      target_user_id)
 | |
|         elif Membership.JOIN == membership:
 | |
|             # Joins are valid iff caller == target and they were:
 | |
|             # invited: They are accepting the invitation
 | |
|             # joined: It's a NOOP
 | |
|             if event.user_id != target_user_id:
 | |
|                 raise AuthError(403, "Cannot force another user to join.")
 | |
|             elif room.is_public:
 | |
|                 pass  # anyone can join public rooms.
 | |
|             elif (not caller or caller.membership not in
 | |
|                     [Membership.INVITE, Membership.JOIN]):
 | |
|                 raise AuthError(403, "You are not invited to this room.")
 | |
|         elif Membership.LEAVE == membership:
 | |
|             if not caller_in_room:  # trying to leave a room you aren't joined
 | |
|                 raise AuthError(403, "You are not in room %s." % event.room_id)
 | |
|             elif target_user_id != event.user_id:
 | |
|                 # trying to force another user to leave
 | |
|                 raise AuthError(403, "Cannot force %s to leave." %
 | |
|                                 target_user_id)
 | |
|         else:
 | |
|             raise AuthError(500, "Unknown membership %s" % membership)
 | |
| 
 | |
|         defer.returnValue(True)
 | |
| 
 | |
|     def get_user_by_req(self, request):
 | |
|         """ Get a registered user's ID.
 | |
| 
 | |
|         Args:
 | |
|             request - An HTTP request with an access_token query parameter.
 | |
|         Returns:
 | |
|             UserID : User ID object of the user making the request
 | |
|         Raises:
 | |
|             AuthError if no user by that token exists or the token is invalid.
 | |
|         """
 | |
|         # Can optionally look elsewhere in the request (e.g. headers)
 | |
|         try:
 | |
|             return self.get_user_by_token(request.args["access_token"][0])
 | |
|         except KeyError:
 | |
|             raise AuthError(403, "Missing access token.")
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def get_user_by_token(self, token):
 | |
|         """ Get a registered user's ID.
 | |
| 
 | |
|         Args:
 | |
|             token (str)- The access token to get the user by.
 | |
|         Returns:
 | |
|             UserID : User ID object of the user who has that access token.
 | |
|         Raises:
 | |
|             AuthError if no user by that token exists or the token is invalid.
 | |
|         """
 | |
|         try:
 | |
|             user_id = yield self.store.get_user_by_token(token=token)
 | |
|             if not user_id:
 | |
|                 raise StoreError()
 | |
|             defer.returnValue(self.hs.parse_userid(user_id))
 | |
|         except StoreError:
 | |
|             raise AuthError(403, "Unrecognised access token.",
 | |
|                             errcode=Codes.UNKNOWN_TOKEN)
 |