Compare commits

..

2 Commits

Author SHA1 Message Date
d33df6d65a check rights 2025-06-11 19:27:51 +02:00
bd0539cff5 role, rolbinding, right daos 2025-06-11 19:07:21 +02:00
15 changed files with 562 additions and 33 deletions

View File

@ -23,9 +23,7 @@ var upgrader = websocket.Upgrader{
} }
func listAvailableChannels(c *gin.Context) { func listAvailableChannels(c *gin.Context) {
token, _ := c.Get(SESSION_COOKIE) channels, err := chatController.ListAvailableChannels(c.GetInt(USER_ID))
channels, err := chatController.ListAvailableChannels(token.(string))
if err != nil { if err != nil {
sendError(c, err) sendError(c, err)
return return
@ -73,9 +71,7 @@ func getMessages(c *gin.Context) {
} }
} }
fmt.Println(from, limit) messages, msgErr := chatController.GetMessages(c.GetInt(USER_ID), id, from, limit)
messages, msgErr := chatController.GetMessages(id, from, limit)
if msgErr != nil { if msgErr != nil {
sendError(c, msgErr) sendError(c, msgErr)
return return
@ -88,8 +84,6 @@ func getMessages(c *gin.Context) {
} }
func sendMessage(c *gin.Context) { func sendMessage(c *gin.Context) {
token, _ := c.Get(SESSION_COOKIE)
message := model.Message{} message := model.Message{}
if err := c.Bind(&message); err != nil { if err := c.Bind(&message); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
@ -98,7 +92,7 @@ func sendMessage(c *gin.Context) {
return return
} }
err := chatController.SendMessage(token.(string), message.Channel, message.Content) err := chatController.SendMessage(c.GetInt(USER_ID), message.Channel, message.Content)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
@ -112,7 +106,6 @@ func sendMessage(c *gin.Context) {
} }
func subscribeToChannel(c *gin.Context) { func subscribeToChannel(c *gin.Context) {
// TODO: check if the user has right to subscribe to the given channel
id, err := strconv.Atoi(c.Param(CHANNEL_ID)) id, err := strconv.Atoi(c.Param(CHANNEL_ID))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
@ -128,7 +121,7 @@ func subscribeToChannel(c *gin.Context) {
} }
defer conn.Close() defer conn.Close()
messages, chanErr := chatController.SubscribeToChannel(c, id) messages, chanErr := chatController.SubscribeToChannel(c, id, c.GetInt(USER_ID))
for { for {
select { select {

View File

@ -35,6 +35,11 @@ func initControlles() *util.ChatError {
return err return err
} }
rightDAO, err := dao.GetRightDAO()
if err != nil {
return err
}
notifcationDAO, err := dao.GetNotificationDAO() notifcationDAO, err := dao.GetNotificationDAO()
if err != nil { if err != nil {
return err return err
@ -47,7 +52,8 @@ func initControlles() *util.ChatError {
sessionDAO, sessionDAO,
messageDAO, messageDAO,
notifcationDAO, notifcationDAO,
userDAO) userDAO,
rightDAO)
return nil return nil
} }

View File

@ -15,33 +15,64 @@ type ChatController struct {
messageDAO dao.IMessageDAO messageDAO dao.IMessageDAO
notifcationDAO dao.INotificationDAO notifcationDAO dao.INotificationDAO
userDAO dao.IUserDAO userDAO dao.IUserDAO
rightDAO dao.IRightDAO
} }
func (c ChatController) ListAvailableChannels(token string) ([]model.Channel, *util.ChatError) { func (c ChatController) ListAvailableChannels(userID int) ([]model.Channel, *util.ChatError) {
userID, err := c.sessionDAO.Get(token) return c.channelDAO.ListAvailableChannels(userID)
}
func (c ChatController) GetMessages(userID int, channel int, from time.Time, limit int) ([]model.Message, *util.ChatError) {
rights, err := c.rightDAO.ListUserRigths(model.User{ID: userID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.channelDAO.ListAvailableChannels(userID) right, ok := rights[channel]
if !ok {
return nil, &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
}
switch right {
case model.RightWrite:
return nil, &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
case model.RightReadWrite:
break
case model.RightAdmin:
break
case model.RightRead:
break
} }
func (c ChatController) GetMessages(channel int, from time.Time, limit int) ([]model.Message, *util.ChatError) {
return c.messageDAO.List(model.Channel{ID: channel}, from, limit) return c.messageDAO.List(model.Channel{ID: channel}, from, limit)
} }
func (c ChatController) SendMessage(token string, channel int, content string) *util.ChatError { func (c ChatController) SendMessage(userID int, channel int, content string) *util.ChatError {
sender_id, err := c.sessionDAO.Get(token) user, err := c.userDAO.Read(model.User{ID: userID})
if err != nil { if err != nil {
return err return err
} }
user, err := c.userDAO.Read(model.User{ID: sender_id}) rights, err := c.rightDAO.ListUserRigths(user)
if err != nil { if err != nil {
return err return err
} }
// TODO: check if user has right to send message in the given channel right, ok := rights[channel]
if !ok {
return &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
}
switch right {
case model.RightWrite:
break
case model.RightReadWrite:
break
case model.RightAdmin:
break
case model.RightRead:
return &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
}
message := model.Message{ message := model.Message{
SenderID: user.ID, SenderID: user.ID,
@ -59,8 +90,36 @@ func (c ChatController) SendMessage(token string, channel int, content string) *
return c.notifcationDAO.SendMessage(message) return c.notifcationDAO.SendMessage(message)
} }
func (c ChatController) SubscribeToChannel(ctx context.Context, id int) (<-chan model.Message, <-chan *util.ChatError) { func (c ChatController) SubscribeToChannel(ctx context.Context, channel int, userID int) (<-chan model.Message, <-chan *util.ChatError) {
return c.notifcationDAO.SubscribeToChannel(ctx, id)
rights, err := c.rightDAO.ListUserRigths(model.User{ID: userID})
if err != nil {
errs := make(chan *util.ChatError, 1)
errs <- err
return nil, errs
}
right, ok := rights[channel]
if !ok {
errs := make(chan *util.ChatError, 1)
errs <- &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
return nil, errs
}
switch right {
case model.RightWrite:
errs := make(chan *util.ChatError, 1)
errs <- &util.ChatError{Message: "", Code: util.SEND_NOT_AUTHORIZED}
return nil, errs
case model.RightReadWrite:
break
case model.RightAdmin:
break
case model.RightRead:
break
}
return c.notifcationDAO.SubscribeToChannel(ctx, channel)
} }
func MakeChatController( func MakeChatController(
@ -68,13 +127,15 @@ func MakeChatController(
sessionDAO dao.ISessionDAO, sessionDAO dao.ISessionDAO,
messageDAO dao.IMessageDAO, messageDAO dao.IMessageDAO,
notificationDAO dao.INotificationDAO, notificationDAO dao.INotificationDAO,
userDAO dao.IUserDAO) ChatController { userDAO dao.IUserDAO,
rightDAO dao.IRightDAO) ChatController {
controller := ChatController{ controller := ChatController{
channelDAO: channelDAO, channelDAO: channelDAO,
sessionDAO: sessionDAO, sessionDAO: sessionDAO,
messageDAO: messageDAO, messageDAO: messageDAO,
notifcationDAO: notificationDAO, notifcationDAO: notificationDAO,
userDAO: userDAO, userDAO: userDAO,
rightDAO: rightDAO,
} }
return controller return controller
} }

View File

@ -11,6 +11,9 @@ var channelDAO IChannelDAO
var sessionDAO ISessionDAO var sessionDAO ISessionDAO
var messageDAO IMessageDAO var messageDAO IMessageDAO
var notifcationDAO INotificationDAO var notifcationDAO INotificationDAO
var roleDAO IRoleDAO
var roleBindingDAO IRoleBindingDAO
var rightDAO IRightDAO
func GetUserDAO() (IUserDAO, *util.ChatError) { func GetUserDAO() (IUserDAO, *util.ChatError) {
if userDAO == nil { if userDAO == nil {
@ -51,6 +54,45 @@ func GetMessageDAO() (IMessageDAO, *util.ChatError) {
return messageDAO, nil return messageDAO, nil
} }
func GetRoleDAO() (IRoleDAO, *util.ChatError) {
if roleDAO == nil {
dao, err := postgres.MakeRoleDAO()
if err != nil {
return roleDAO, err
}
roleDAO = dao
}
return roleDAO, nil
}
func GetRoleBindingDAO() (IRoleBindingDAO, *util.ChatError) {
if roleBindingDAO == nil {
dao, err := postgres.MakeRoleBindingDAO()
if err != nil {
return roleBindingDAO, err
}
roleBindingDAO = dao
}
return roleBindingDAO, nil
}
func GetRightDAO() (IRightDAO, *util.ChatError) {
if rightDAO == nil {
dao, err := postgres.MakeRightDAO()
if err != nil {
return rightDAO, err
}
rightDAO = dao
}
return rightDAO, nil
}
func GetSessionDAO() (ISessionDAO, *util.ChatError) { func GetSessionDAO() (ISessionDAO, *util.ChatError) {
if sessionDAO == nil { if sessionDAO == nil {
dao, err := valkey.MakeSessionDAO() dao, err := valkey.MakeSessionDAO()

14
dao/IRightDAO.go Normal file
View File

@ -0,0 +1,14 @@
package dao
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
)
type IRightDAO interface {
Grant(model.Role, model.Channel, model.RightEnum) *util.ChatError
Update(model.Role, model.Channel, model.RightEnum) *util.ChatError
Revoke(model.Role, model.Channel) *util.ChatError
ListRoleRights(model.Role) (map[int]model.RightEnum, *util.ChatError)
ListUserRigths(model.User) (map[int]model.RightEnum, *util.ChatError)
}

13
dao/IRoleBindingDAO.go Normal file
View File

@ -0,0 +1,13 @@
package dao
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
)
type IRoleBindingDAO interface {
Bind(model.Role, model.User) *util.ChatError
Unbind(model.Role, model.User) *util.ChatError
ListRolesOfUser(model.User) ([]model.Role, *util.ChatError)
ListUserWithRole(model.Role) ([]model.User, *util.ChatError)
}

14
dao/IRoleDAO.go Normal file
View File

@ -0,0 +1,14 @@
package dao
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
)
type IRoleDAO interface {
Create(name string) (int, *util.ChatError)
Read(model.Role) (model.Role, *util.ChatError)
List() ([]model.Role, *util.ChatError)
Update(model.Role) *util.ChatError
Delete(model.Role) *util.ChatError
}

View File

@ -60,7 +60,7 @@ func (d ChannelDAOPG) List() ([]model.Channel, *util.ChatError) {
// ListAvailableChannels returns channels that the given user has access to // ListAvailableChannels returns channels that the given user has access to
func (d ChannelDAOPG) ListAvailableChannels(userID int) ([]model.Channel, *util.ChatError) { func (d ChannelDAOPG) ListAvailableChannels(userID int) ([]model.Channel, *util.ChatError) {
rows, err := d.db.Queryx(`select "channel_id" as "id", "channel_name" as "name", "channel_description" as "description" from "user_rigths_per_channel" where "user_id" = $1 order by "id"`, userID) rows, err := d.db.Queryx(`select "channel_id" as "id", "channel_name" as "name", "channel_description" as "description", "rights" from "user_rigths_per_channel" where "user_id" = $1 order by "id"`, userID)
if err != nil { if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT) return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
} }

146
dao/postgres/RightDAO.go Normal file
View File

@ -0,0 +1,146 @@
package postgres
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
)
type RightDAOPG struct {
pgDAO
}
func (d RightDAOPG) Grant(role model.Role, channel model.Channel, rights model.RightEnum) *util.ChatError {
if role.ID != 0 {
role.Name = ""
}
if channel.ID != 0 {
channel.Name = ""
}
_, err := d.db.Exec(
`insert into "right" ("role_id", "channel_id", "rights") values (
(select "id" from "role" where "id" = $1 or "name" = $2),
(select "id" from "channel" where "id" = $3 or "name" = $4),
$5
)`, role.ID, role.Name, channel.ID, channel.Name, rights)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RightDAOPG) Revoke(role model.Role, channel model.Channel) *util.ChatError {
if role.ID != 0 {
role.Name = ""
}
if channel.ID != 0 {
channel.Name = ""
}
_, err := d.db.Exec(
`delete from "right"
where
"role_id" = (select "id" from "role" where "id" = $1 or "name" = $2)
and
"channel_id" = (select "id" from "channel" where "id" = $3 or "name" = $4)`,
role.ID, role.Name, channel.ID, channel.Name)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RightDAOPG) Update(role model.Role, channel model.Channel, rights model.RightEnum) *util.ChatError {
if role.ID != 0 {
role.Name = ""
}
if channel.ID != 0 {
channel.Name = ""
}
_, err := d.db.Exec(
`update "right" set "rights" = $5
where
"role_id" = (select "id" from "role" where "id" = $1 or "name" = $2)
and
"channel_id" = (select "id" from "channel" where "id" = $3 or "name" = $4)`,
role.ID, role.Name, channel.ID, channel.Name, rights)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RightDAOPG) ListRoleRights(role model.Role) (map[int]model.RightEnum, *util.ChatError) {
if role.ID != 0 {
role.Name = ""
}
rows, err := d.db.NamedQuery(
`select
"channel_id", "rights"
from "rights"
where "role_id" = (select "id" from "role" where "id" = :id or "name" = :name)`,
&role)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
result := make(map[int]model.RightEnum)
var channel_id int
var rights model.RightEnum
for rows.Next() {
err = rows.Scan(&channel_id, &rights)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
result[channel_id] = rights
}
return result, nil
}
func (d RightDAOPG) ListUserRigths(user model.User) (map[int]model.RightEnum, *util.ChatError) {
if user.ID != 0 {
user.Username = ""
}
rows, err := d.db.NamedQuery(
`select
"channel_id", "rights"
from "user_rigths_per_channel"
where "user_id" = (select "id" from "user" where "id" = :id or "username" = :username)`,
&user)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
result := make(map[int]model.RightEnum)
var channel_id int
var rights model.RightEnum
for rows.Next() {
err = rows.Scan(&channel_id, &rights)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
result[channel_id] = rights
}
return result, nil
}
func MakeRightDAO() (RightDAOPG, *util.ChatError) {
dao := RightDAOPG{}
conn, err := getDatabase()
if err != nil {
return dao, err
}
dao.db = conn
return dao, nil
}

View File

@ -0,0 +1,131 @@
package postgres
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
)
type RoleBindingDAOPG struct {
pgDAO
}
// Bind creates a new RoleBinding.
//
// It uses name only if ID is not set.
func (d RoleBindingDAOPG) Bind(role model.Role, user model.User) *util.ChatError {
if role.ID != 0 {
role.Name = ""
}
if user.ID != 0 {
user.Username = ""
}
_, err := d.db.Exec(
`insert into "role" ("role_id", "user_id") values (
(select "id" from "role" where "id" = $1 or "name" = $2),
(select "id" from "user" where "id" = $3 or "username" = $4))`,
role.ID, role.Name,
user.ID, user.Username)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
// Unbind deletes a RoleBinding.
//
// It uses name only if ID is not set.
func (d RoleBindingDAOPG) Unbind(role model.Role, user model.User) *util.ChatError {
if role.ID != 0 {
role.Name = ""
}
if user.ID != 0 {
user.Username = ""
}
_, err := d.db.Exec(
`delete from "role"
where
"role_id" = (select "id" from "role" where "id" = $1 or "name" = $2)
and
"user_id" = (select "id" from "user" where "id" = $3 or "username" = $4)`,
role.ID, role.Name,
user.ID, user.Username)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RoleBindingDAOPG) ListRolesOfUser(user model.User) ([]model.Role, *util.ChatError) {
if user.ID != 0 {
user.Username = ""
}
rows, err := d.db.NamedQuery(
`select
"r"."id" as "id",
"r"."name" as "name"
from "role_binding" "rb"
join "role" "r" on "r"."id" = "rb"."role_id"
where "user_id" = (select "id" from "user" where "id" = :id or "username" = :username)`,
&user)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
var roles []model.Role
for rows.Next() {
role := model.Role{}
err = rows.Scan(&role)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
roles = append(roles, role)
}
return roles, nil
}
func (d RoleBindingDAOPG) ListUserWithRole(role model.Role) ([]model.User, *util.ChatError) {
if role.ID != 0 {
role.Name = ""
}
rows, err := d.db.NamedQuery(
`select
"u".*
from "user" "u"
join "role_binding" "rb" on "rb"."user_id" = "u"."id"
where "rb"."role_id" = (select "id" from "role" where "id" = :id or "name" = :name)`,
&role)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
var users []model.User
for rows.Next() {
user := model.User{}
err = rows.Scan(&user)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
users = append(users, user)
}
return users, nil
}
func MakeRoleBindingDAO() (RoleBindingDAOPG, *util.ChatError) {
dao := RoleBindingDAOPG{}
conn, err := getDatabase()
if err != nil {
return dao, err
}
dao.db = conn
return dao, nil
}

98
dao/postgres/RoleDAO.go Normal file
View File

@ -0,0 +1,98 @@
package postgres
import (
"git.tek.govt.hu/dowerx/chat/server/model"
"git.tek.govt.hu/dowerx/chat/server/util"
"github.com/jmoiron/sqlx"
)
type RoleDAOPG struct {
pgDAO
}
func (d RoleDAOPG) Create(name string) (int, *util.ChatError) {
rows, err := d.db.Query(`insert into "role" ("name") values ($1) returning "id"`, name)
if err != nil {
return 0, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
if !rows.Next() {
return 0, &util.ChatError{Message: "no id returned by insert", Code: util.DATABASE_QUERY_FAULT}
}
var id int
err = rows.Scan(&id)
return id, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RoleDAOPG) Read(role model.Role) (model.Role, *util.ChatError) {
var rows *sqlx.Rows
var err error
if role.ID != 0 {
rows, err = d.db.NamedQuery(`select * from "role" where "id" = :id`, &role)
} else {
rows, err = d.db.NamedQuery(`select * from "role" where "name" = :name`, &role)
}
defer rows.Close()
if err != nil {
return role, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
if !rows.Next() {
return role, &util.ChatError{Message: "", Code: util.NOT_FOUND}
}
err = rows.StructScan(&role)
return role, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RoleDAOPG) List() ([]model.Role, *util.ChatError) {
rows, err := d.db.Queryx(`select * from "role" order by "name"`)
if err != nil {
return nil, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
defer rows.Close()
roles := make([]model.Role, 0)
for rows.Next() {
role := model.Role{}
err = rows.StructScan(&role)
if err != nil {
break
}
roles = append(roles, role)
}
return roles, util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RoleDAOPG) Update(role model.Role) *util.ChatError {
_, err := d.db.NamedExec(`update "role" set "name" = :name where "id" = :id`, &role)
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func (d RoleDAOPG) Delete(role model.Role) *util.ChatError {
var err error
if role.ID != 0 {
_, err = d.db.NamedExec(`delete from "role" where "id" = :id`, &role)
} else {
_, err = d.db.NamedExec(`delete from "role" where "name" = :name`, &role)
}
return util.MakeError(err, util.DATABASE_QUERY_FAULT)
}
func MakeRoleDAO() (RoleDAOPG, *util.ChatError) {
dao := RoleDAOPG{}
conn, err := getDatabase()
if err != nil {
return dao, err
}
dao.db = conn
return dao, nil
}

View File

@ -4,4 +4,5 @@ type Channel struct {
ID int `db:"id" json:"id"` ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"` Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"` Description string `db:"description" json:"description"`
Rights RightEnum `db:"rights" json:"rights"`
} }

View File

@ -1,10 +1,10 @@
package model package model
type Right string type RightEnum string
const ( const (
RightRead = "R" RightRead RightEnum = "R"
RightWrite = "W" RightWrite RightEnum = "W"
RightReadWrite = "RW" RightReadWrite RightEnum = "RW"
RightAdmin = "A" RightAdmin RightEnum = "A"
) )

6
model/Role.go Normal file
View File

@ -0,0 +1,6 @@
package model
type Role struct {
ID int `db:"id"`
Name string `db:"name"`
}

View File

@ -18,6 +18,8 @@ const (
USERNAME_TOO_SHORT USERNAME_TOO_SHORT
PASSWORD_TOO_SHORT PASSWORD_TOO_SHORT
PASSWORDS_DONT_MATCH PASSWORDS_DONT_MATCH
SEND_NOT_AUTHORIZED
READ_NOT_AUTHORIZED
NOT_LOGGED_IN NOT_LOGGED_IN
) )
@ -31,6 +33,8 @@ var codeToMessage = map[ChatErrorCode]string{
USERNAME_TOO_SHORT: "username is too short", USERNAME_TOO_SHORT: "username is too short",
PASSWORD_TOO_SHORT: "password is too short", PASSWORD_TOO_SHORT: "password is too short",
PASSWORDS_DONT_MATCH: "passwords do not match", PASSWORDS_DONT_MATCH: "passwords do not match",
SEND_NOT_AUTHORIZED: "not authorized to send messages in this channel",
READ_NOT_AUTHORIZED: "not authorized to read messages in this channel",
NOT_LOGGED_IN: "not logged in", NOT_LOGGED_IN: "not logged in",
} }