Coverage for src/mcp_atlassian/jira/users.py: 94%
63 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 03:26 +0900
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 03:26 +0900
1"""Module for Jira user operations."""
3import logging
4import re
5from typing import Optional, Dict, Any, cast
7import requests
9from .client import JiraClient
11logger = logging.getLogger("mcp-jira")
14class UsersMixin(JiraClient):
15 """Mixin for Jira user operations."""
17 def get_current_user_account_id(self) -> str:
18 """Get the account ID of the current user.
20 Returns:
21 Account ID of the current user
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
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
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)
42 def _get_account_id(self, assignee: str) -> str:
43 """Get the account ID for a username.
45 Args:
46 assignee: Username or account ID
48 Returns:
49 Account ID
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
58 # First try direct lookup
59 account_id = self._lookup_user_directly(assignee)
60 if account_id:
61 return account_id
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
68 error_msg = f"Could not find account ID for user: {assignee}"
69 raise ValueError(error_msg)
71 def _lookup_user_directly(self, username: str) -> Optional[str]:
72 """Look up a user account ID directly.
74 Args:
75 username: Username to look up
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
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
97 def _lookup_user_by_permissions(self, username: str) -> Optional[str]:
98 """Look up a user account ID by permissions.
100 This is a fallback method when direct lookup fails.
102 Args:
103 username: Username to look up
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"}
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 "")
121 response = requests.get(
122 url,
123 params=params,
124 auth=auth,
125 headers=headers,
126 verify=self.config.ssl_verify,
127 )
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