Files
houston-be/service/auth_service.go
Vijay Joshi 804be01c2f INFRA-3467 : Private Houston Incidents (#445)
* INFRA-3467 : Private Houston Incidents

* INFRA-3627 : Minor self review

* INFRA-3627 : PR Review changes

INFRA-3627 : Minor changes

INFRA-3627 : UT fix

INFRA-3637 : Message changes

INFRA-3627 : Minor changes

INFRA-3627 : Constant fix

INFRA-3627 : Do not post SLA breach in public channels for private incidents
2024-08-08 19:20:04 +05:30

315 lines
10 KiB
Go

package service
import (
"fmt"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"houston/appcontext"
"houston/common/util"
"houston/internal/clients"
"houston/logger"
clientsResponse "houston/model/clients"
"houston/pkg/slackbot"
"houston/service/incident"
"houston/service/request/team"
common "houston/service/response/common"
"houston/service/teamUser"
"houston/service/user"
"net/http"
"strconv"
"strings"
)
type AuthService struct {
mjolnirClient *clients.MjolnirClient
slackClient *slackbot.Client
userService user.UserService
teamUserService teamUser.ITeamUserService
incidentService incident.IIncidentService
}
func NewAuthService(mjolnirClient *clients.MjolnirClient, slackClient *slackbot.Client) *AuthService {
return &AuthService{
mjolnirClient: mjolnirClient,
slackClient: slackClient,
userService: appcontext.GetUserService(),
teamUserService: appcontext.GetTeamUserService(),
incidentService: appcontext.GetIncidentService(),
}
}
type UserRoles string
const (
Admin UserRoles = "Admin"
Manager UserRoles = "Manager"
)
func (authService *AuthService) checkIfManagerOrAdmin(
context *gin.Context,
managerSlackHandle string,
roles ...UserRoles,
) (bool, error) {
headerData := getSessionTokenAndUserEmail(context)
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(headerData.sessionToken)
if err != nil || sessionResponse.StatusCode == 401 {
logger.Error(fmt.Sprintf("error occurred while getting session data from mjolnir for %v", headerData.userEmail), zap.Error(err))
return false, nil
}
if sessionResponse.EmailId != headerData.userEmail {
logger.Error(fmt.Sprintf("user email: %v does not match the email linked to the session token", headerData.userEmail))
return false, nil
}
userRoles := strings.Join(sessionResponse.Roles, ",")
var expectedRoles string
for _, role := range roles {
expectedRoles += string(role) + ","
}
if strings.Contains(expectedRoles, "Manager") {
userInfo, err := authService.slackClient.GetUserByEmail(headerData.userEmail)
if err != nil {
logger.Error(fmt.Sprintf("User with email %v was not found in slack", headerData.userEmail), zap.Error(err))
return false, nil
}
if userInfo.ID == managerSlackHandle {
return true, nil
}
}
return strings.Contains(userRoles, string(Admin)), nil
}
func (authService *AuthService) isAdmin(sessionResponse *clientsResponse.MjolnirSessionResponse) bool {
if !strings.Contains(strings.Join(sessionResponse.Roles, ","), string(Admin)) {
logger.Error(fmt.Sprintf("user is not an admin. Roles are: %s", sessionResponse.Roles))
return false
}
return true
}
func (authService *AuthService) validateUserAndGetSessionResponse(sessionToken, emailID string) (*clientsResponse.MjolnirSessionResponse, error) {
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(sessionToken)
if err != nil || sessionResponse.StatusCode == http.StatusUnauthorized {
logger.Error(fmt.Sprintf("error occurred while getting session data from mjolnir for %v", sessionToken), zap.Error(err))
return nil, err
}
if sessionResponse.EmailId != emailID {
logger.Error(fmt.Sprintf("user email: %v does not match the email linked to the session token", emailID))
return nil, fmt.Errorf("user email: %v does not match the email linked to the session token", emailID)
}
return sessionResponse, nil
}
func (authService *AuthService) isManager(teamId uint, userEmail string) bool {
teamUser, err := authService.teamUserService.GetTeamUserByTeamIdAndUserEmailId(teamId, userEmail)
if err != nil {
logger.Error(fmt.Sprintf("error occurred while getting team user with team id: %d and email: %s", teamId, userEmail), zap.Error(err))
return false
}
if teamUser == nil {
logger.Info(fmt.Sprintf("user: %s is not a member of team: %d", userEmail, teamId))
return false
}
if teamUser.User.SlackUserId != teamUser.Team.ManagerHandle {
logger.Info(fmt.Sprintf("user: %s is not the manager of team: %d", userEmail, teamId))
return false
}
return true
}
func (authService *AuthService) isMember(teamId uint, userEmail string) bool {
teamUser, err := authService.teamUserService.GetTeamUserByTeamIdAndUserEmailId(teamId, userEmail)
if err != nil {
logger.Error("error in fetching team users", zap.Error(err))
return false
}
if teamUser != nil {
return true
}
return false
}
func (authService *AuthService) IfAdmin(fn gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
headerData := getSessionTokenAndUserEmail(c)
sessionResponse, err := authService.validateUserAndGetSessionResponse(headerData.sessionToken, headerData.userEmail)
if err != nil || sessionResponse == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
if !authService.isAdmin(sessionResponse) {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
fn(c)
}
}
func (authService *AuthService) IfValidHoustonUser(fn gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
headerData := getSessionTokenAndUserEmail(c)
sessionResponse, err := authService.validateUserAndGetSessionResponse(headerData.sessionToken, headerData.userEmail)
if err != nil || sessionResponse == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
fn(c)
}
}
func (authService *AuthService) IfManagerOrAdmin(fn gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
headerData := getSessionTokenAndUserEmail(c)
sessionResponse, err := authService.validateUserAndGetSessionResponse(headerData.sessionToken, headerData.userEmail)
if err != nil || sessionResponse == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
teamId, err := strconv.Atoi(c.Param(util.IdParam))
if err != nil {
logger.Error("error occurred while parsing team id", zap.Error(err))
c.AbortWithStatus(http.StatusBadRequest)
return
}
if !authService.isAdmin(sessionResponse) && !authService.isManager(uint(teamId), headerData.userEmail) {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
fn(c)
}
}
func (authService *AuthService) IfMemberOrAdmin(fn func(*gin.Context, team.UpdateTeamRequest)) gin.HandlerFunc {
return func(c *gin.Context) {
headerData := getSessionTokenAndUserEmail(c)
sessionResponse, err := authService.validateUserAndGetSessionResponse(headerData.sessionToken, headerData.userEmail)
if err != nil || sessionResponse == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
var updateTeamRequest team.UpdateTeamRequest
if err := c.ShouldBindJSON(&updateTeamRequest); err != nil {
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
return
}
if !authService.isAdmin(sessionResponse) && !authService.isMember(updateTeamRequest.Id, headerData.userEmail) {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
//sending request object as a parameter to avoid EOF
fn(c, updateTeamRequest)
}
}
func (authService *AuthService) checkIfAdminOrTeamMember(context *gin.Context, teamMembers []string) (bool, error) {
headerData := getSessionTokenAndUserEmail(context)
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(headerData.sessionToken)
if err != nil || sessionResponse.StatusCode == 401 {
logger.Error(fmt.Sprintf("error occurred while getting session data from mjolnir for %v", headerData.userEmail), zap.Error(err))
return false, nil
}
if sessionResponse.EmailId != headerData.userEmail {
logger.Error(fmt.Sprintf("user email: %v does not match the email linked to the session token", headerData.userEmail))
return false, nil
}
if teamMembers == nil || len(teamMembers) == 0 {
return strings.Contains(strings.Join(sessionResponse.Roles, ","), string(Admin)), nil
}
userInfo, err := authService.slackClient.GetUserByEmail(headerData.userEmail)
if err != nil {
logger.Error(fmt.Sprintf("User with email %v was not found in slack", headerData.userEmail), zap.Error(err))
return false, nil
}
return strings.Contains(strings.Join(sessionResponse.Roles, ","), string(Admin)) ||
strings.Contains(strings.Join(teamMembers, ","), userInfo.ID), nil
}
func (authService *AuthService) CanUserAccessIncident(fn gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
incidentId, err := strconv.Atoi(c.Param(util.IdParam))
if err != nil {
logger.Error("error occurred while parsing incident id", zap.Error(err))
c.AbortWithStatus(http.StatusBadRequest)
return
}
if !authService.incidentService.CanUserWithEmailAccessIncidentWithId(
c.Request.Header.Get(util.UserEmailHeader), uint(incidentId),
) {
logger.Error("user is not allowed to access incident data")
c.JSON(http.StatusNotFound, common.ErrorResponse(fmt.Errorf("could not find incident with id %d", incidentId), http.StatusNotFound, nil))
return
}
fn(c)
}
}
func (authService *AuthService) CanUserAccessIncidentLogs(fn gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
logType := c.Param("log_type")
if logType == util.IncidentLogType {
incidentId, err := strconv.Atoi(c.Param(util.IdParam))
if err != nil {
logger.Error("error occurred while parsing incident id", zap.Error(err))
c.AbortWithStatus(http.StatusBadRequest)
return
}
if !authService.incidentService.CanUserWithEmailAccessIncidentWithId(
c.Request.Header.Get(util.UserEmailHeader), uint(incidentId),
) {
logger.Error("user is not allowed to access incident logs")
c.JSON(http.StatusNotFound, common.ErrorResponse(fmt.Errorf("could not find incident with id %d", incidentId), http.StatusNotFound, nil))
return
}
}
fn(c)
}
}
func (authService *AuthService) CheckValidUser(sessionToken, userEmail string) (bool, error) {
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(sessionToken)
if err != nil || sessionResponse.StatusCode == http.StatusUnauthorized {
logger.Error(fmt.Sprintf("error occurred while getting session data from mjolnir for %v", userEmail), zap.Error(err))
return false, err
}
if sessionResponse.EmailId != userEmail {
logger.Error(fmt.Sprintf("user email: %v does not match the email linked to the session token", userEmail))
return false, fmt.Errorf("user email: %v does not match the email linked to the session token", userEmail)
}
return true, nil
}
func getSessionTokenAndUserEmail(context *gin.Context) *headersData {
return &headersData{
sessionToken: context.Request.Header.Get(util.SessionTokenHeader),
userEmail: context.Request.Header.Get(util.UserEmailHeader),
}
}
type headersData struct {
sessionToken string
userEmail string
}