* 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
315 lines
10 KiB
Go
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
|
|
}
|