Coverage for src/mcp_atlassian/jira/users.py: 94%

1"""Module for Jira user operations."""

2

3 import logging

4 import re

5 from typing import Optional, Dict, Any, cast

6

7 import requests

8

9 from .client import JiraClient

10

11 logger = logging.getLogger("mcp-jira")

12

13

14 class UsersMixin(JiraClient):

15 """Mixin for Jira user operations."""

16

17 def get_current_user_account_id(self) -> str:

18 """Get the account ID of the current user.

19

20 Returns:

21 Account ID of the current user

22

23 Raises:

24 Exception: If unable to get the current user's account ID

25 """

26 if self._current_user_account_id is not None:

27 return self._current_user_account_id

28

29 try:

30 myself = self.jira.myself()

31 if "accountId" in myself:

32 self._current_user_account_id = myself["accountId"]

33 return self._current_user_account_id

34

35 error_msg = "Could not find accountId in user data"

36 raise ValueError(error_msg)

37 except Exception as e:

38 logger.error(f"Error getting current user account ID: {str(e)}")

39 error_msg = f"Unable to get current user account ID: {str(e)}"

40 raise Exception(error_msg)

41

42 def _get_account_id(self, assignee: str) -> str:

43 """Get the account ID for a username.

44

45 Args:

46 assignee: Username or account ID

47

48 Returns:

49 Account ID

50

51 Raises:

52 ValueError: If the account ID could not be found

53 """

54 # If it looks like an account ID already, return it

55 if assignee.startswith("5") and len(assignee) >= 10:

56 return assignee

57

58 # First try direct lookup

59 account_id = self._lookup_user_directly(assignee)

60 if account_id:

61 return account_id

62

63 # If that fails, try permissions-based lookup

64 account_id = self._lookup_user_by_permissions(assignee)

65 if account_id:

66 return account_id

67

68 error_msg = f"Could not find account ID for user: {assignee}"

69 raise ValueError(error_msg)

70

71 def _lookup_user_directly(self, username: str) -> Optional[str]:

72 """Look up a user account ID directly.

73

74 Args:

75 username: Username to look up

76

77 Returns:

78 Account ID if found, None otherwise

79 """

80 try:

81 # Try to find user

82 response = self.jira.user_find_by_user_string(query=username, start=0, limit=1)

83 if not response:

84 return None

85

86 for user in response:

87 if ("accountId" in user and

88 (user.get("displayName", "").lower() == username.lower() or

89 user.get("name", "").lower() == username.lower() or

90 user.get("emailAddress", "").lower() == username.lower())):

91 return user["accountId"]

92 return None

93 except Exception as e:

94 logger.info(f"Error looking up user directly: {str(e)}")

95 return None

96

97 def _lookup_user_by_permissions(self, username: str) -> Optional[str]:

98 """Look up a user account ID by permissions.

99

100 This is a fallback method when direct lookup fails.

101

102 Args:

103 username: Username to look up

104

105 Returns:

106 Account ID if found, None otherwise

107 """

108 try:

109 # Try to find user who has permissions for a project

110 # This approach helps when regular lookup fails due to permissions

111 url = f"{self.config.url}/rest/api/2/user/permission/search"

112 params = {"query": username, "permissions": "BROWSE"}

113

114 auth = None

115 headers = {}

116 if self.config.auth_type == "token":

117 headers["Authorization"] = f"Bearer {self.config.personal_token}"

118 else:

119 auth = (self.config.username or "", self.config.api_token or "")

120

121 response = requests.get(

122 url,

123 params=params,

124 auth=auth,

125 headers=headers,

126 verify=self.config.ssl_verify,

127 )

128

129 if response.status_code == 200:

130 data = response.json()

131 for user in data.get("users", []):

132 if "accountId" in user:

133 return user["accountId"]

134 return None

135 except Exception as e:

136 logger.info(f"Error looking up user by permissions: {str(e)}")

137 return None