mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
NOISSUE - Add access control listing in alarms, rules engine and reports (#3417)
Signed-off-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
+36
-18
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
const alarmColumns = `alarms.id, alarms.rule_id, alarms.domain_id, alarms.channel_id, alarms.client_id, alarms.subtopic, alarms.measurement, alarms.value, alarms.unit,
|
||||
alarms.threshold, alarms.cause, alarms.status, alarms.severity, alarms.assignee_id, alarms.created_at, alarms.updated_at, alarms.updated_by, alarms.assigned_at,
|
||||
alarms.threshold, alarms.cause, alarms.status, alarms.severity, alarms.assignee_id, alarms.created_at, alarms.updated_at, alarms.updated_by, alarms.assigned_at,
|
||||
alarms.assigned_by, alarms.acknowledged_at, alarms.acknowledged_by, alarms.resolved_at, alarms.resolved_by, alarms.metadata`
|
||||
|
||||
type repository struct {
|
||||
@@ -134,8 +134,8 @@ func (r *repository) UpdateAlarm(ctx context.Context, alarm alarms.Alarm) (alarm
|
||||
}
|
||||
|
||||
q := fmt.Sprintf(`UPDATE alarms SET %s updated_by = :updated_by, updated_at = :updated_at WHERE id = :id
|
||||
RETURNING id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit, threshold,
|
||||
cause, status, severity, assignee_id, assigned_at, assigned_by, acknowledged_at, acknowledged_by,
|
||||
RETURNING id, rule_id, domain_id, channel_id, client_id, subtopic, measurement, value, unit, threshold,
|
||||
cause, status, severity, assignee_id, assigned_at, assigned_by, acknowledged_at, acknowledged_by,
|
||||
resolved_by, resolved_at, metadata, created_at, updated_by, updated_at;`, upq)
|
||||
|
||||
dba, err := toDBAlarm(alarm)
|
||||
@@ -199,17 +199,30 @@ func (r *repository) ListAllAlarms(ctx context.Context, pm alarms.PageMetadata)
|
||||
}
|
||||
|
||||
func (r *repository) ListUserAlarms(ctx context.Context, userID string, pm alarms.PageMetadata) (alarms.AlarmsPage, error) {
|
||||
query, err := pageQuery(pm)
|
||||
if err != nil {
|
||||
return alarms.AlarmsPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
clauses := []string{
|
||||
`(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM rules_roles rr
|
||||
JOIN rules_role_members rrm ON rrm.role_id = rr.id
|
||||
WHERE rr.entity_id = alarms.rule_id AND rrm.member_id = :user_id
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM domains_roles dr
|
||||
JOIN domains_role_members drm ON drm.role_id = dr.id
|
||||
JOIN domains_role_actions dra ON dra.role_id = dr.id
|
||||
WHERE dr.entity_id = alarms.domain_id
|
||||
AND drm.member_id = :user_id
|
||||
AND dra.action LIKE 'alarm%'
|
||||
)
|
||||
)`,
|
||||
}
|
||||
|
||||
clauses = append(clauses, pageQueryConditions(pm)...)
|
||||
query := fmt.Sprintf("WHERE %s", strings.Join(clauses, " AND "))
|
||||
pm.UserID = userID
|
||||
comQuery := fmt.Sprintf(`SELECT DISTINCT %s
|
||||
FROM alarms
|
||||
INNER JOIN rules_roles rr ON rr.entity_id = alarms.rule_id
|
||||
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
|
||||
%s`, alarmColumns, query)
|
||||
comQuery := fmt.Sprintf(`SELECT DISTINCT %s FROM alarms %s`, alarmColumns, query)
|
||||
|
||||
return r.alarmsPage(ctx, comQuery, pm)
|
||||
}
|
||||
@@ -462,6 +475,17 @@ func toAlarm(dbr dbAlarm) (alarms.Alarm, error) {
|
||||
}
|
||||
|
||||
func pageQuery(pm alarms.PageMetadata) (string, error) {
|
||||
query := pageQueryConditions(pm)
|
||||
|
||||
var emq string
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return emq, nil
|
||||
}
|
||||
|
||||
func pageQueryConditions(pm alarms.PageMetadata) []string {
|
||||
var query []string
|
||||
if pm.DomainID != "" {
|
||||
query = append(query, "alarms.domain_id = :domain_id")
|
||||
@@ -508,11 +532,5 @@ func pageQuery(pm alarms.PageMetadata) (string, error) {
|
||||
if !pm.CreatedTo.IsZero() {
|
||||
query = append(query, "alarms.created_at <= :created_to")
|
||||
}
|
||||
|
||||
var emq string
|
||||
if len(query) > 0 {
|
||||
emq = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return emq, nil
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -417,7 +417,15 @@ func TestListAlarms(t *testing.T) {
|
||||
|
||||
func TestListUserAlarms(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM alarms")
|
||||
_, err := db.Exec("DELETE FROM domains_role_actions")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_role_actions unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_role_members")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_roles")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM alarms")
|
||||
require.Nil(t, err, fmt.Sprintf("clean alarms unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM rules")
|
||||
require.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
|
||||
@@ -426,9 +434,14 @@ func TestListUserAlarms(t *testing.T) {
|
||||
repo := postgres.NewAlarmsRepo(db)
|
||||
|
||||
domainID := generateUUID(t)
|
||||
domainRoute := generateUUID(t)
|
||||
userID := generateUUID(t)
|
||||
otherUserID := generateUUID(t)
|
||||
adminUserID := generateUUID(t)
|
||||
domainUserID := generateUUID(t)
|
||||
|
||||
_, err := db.Exec(`INSERT INTO domains (id, name, route, status) VALUES ($1, $2, $3, $4)`, domainID, namegen.Generate(), domainRoute, 0)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains unexpected error: %s", err))
|
||||
|
||||
// Create 10 rules and 10 alarms referencing them.
|
||||
// Assign userID to the first 6 rules via role membership.
|
||||
@@ -485,6 +498,14 @@ func TestListUserAlarms(t *testing.T) {
|
||||
require.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
domainRoleID := generateUUID(t)
|
||||
_, err = db.Exec(`INSERT INTO domains_roles (id, name, entity_id) VALUES ($1, $2, $3)`, domainRoleID, "admin", domainID)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, domainRoleID, domainUserID, domainID)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_actions (role_id, action) VALUES ($1, $2)`, domainRoleID, "alarm_read")
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_role_actions unexpected error: %s", err))
|
||||
|
||||
_ = createdAlarms
|
||||
|
||||
cases := []struct {
|
||||
@@ -566,6 +587,16 @@ func TestListUserAlarms(t *testing.T) {
|
||||
count: 10,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list alarms for user with domain-level rule access returns all alarms",
|
||||
userID: domainUserID,
|
||||
pm: alarms.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
count: 10,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user alarms ordered by created_at ascending",
|
||||
userID: userID,
|
||||
|
||||
+33
-14
@@ -404,22 +404,37 @@ func (repo *PostgresRepository) ListAllRules(ctx context.Context, pm re.PageMeta
|
||||
|
||||
func (repo *PostgresRepository) ListUserRules(ctx context.Context, userID string, pm re.PageMeta) (re.Page, error) {
|
||||
pm.UserID = userID
|
||||
pq := pageRulesQuery(pm)
|
||||
clauses := []string{
|
||||
`(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM rules_roles rr
|
||||
JOIN rules_role_members rrm ON rrm.role_id = rr.id
|
||||
WHERE rr.entity_id = r.id AND rrm.member_id = :user_id
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM domains_roles dr
|
||||
JOIN domains_role_members drm ON drm.role_id = dr.id
|
||||
JOIN domains_role_actions dra ON dra.role_id = dr.id
|
||||
WHERE dr.entity_id = r.domain_id
|
||||
AND drm.member_id = :user_id
|
||||
AND dra.action LIKE 'rule%'
|
||||
)
|
||||
)`,
|
||||
}
|
||||
clauses = append(clauses, pageRulesQueryConditions(pm)...)
|
||||
orderClause := rulesOrderClause(pm)
|
||||
pgData := rulesPageData(pm)
|
||||
|
||||
userJoin := `
|
||||
INNER JOIN rules_roles rr ON rr.entity_id = r.id
|
||||
INNER JOIN rules_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
|
||||
`
|
||||
whereClause := fmt.Sprintf("WHERE %s", strings.Join(clauses, " AND "))
|
||||
|
||||
innerQ := fmt.Sprintf(`
|
||||
SELECT DISTINCT r.id, r.name, r.domain_id, r.tags, r.input_channel, r.input_topic, r.logic_type, r.logic_value, r.outputs,
|
||||
r.start_datetime, r.time, r.recurring, r.recurring_period, r.created_at, r.created_by, r.updated_at, r.updated_by, r.status
|
||||
FROM rules r
|
||||
%s
|
||||
%s
|
||||
`, userJoin, pq)
|
||||
`, whereClause)
|
||||
|
||||
q := fmt.Sprintf(`
|
||||
SELECT * FROM (%s) AS sub %s %s;
|
||||
@@ -521,6 +536,16 @@ func rulesPageData(pm re.PageMeta) string {
|
||||
}
|
||||
|
||||
func pageRulesQuery(pm re.PageMeta) string {
|
||||
query := pageRulesQueryConditions(pm)
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
func pageRulesQueryConditions(pm re.PageMeta) []string {
|
||||
var query []string
|
||||
if pm.InputChannel != "" {
|
||||
query = append(query, "r.input_channel = :input_channel")
|
||||
@@ -546,11 +571,5 @@ func pageRulesQuery(pm re.PageMeta) string {
|
||||
if pm.Scheduled != nil && !*pm.Scheduled {
|
||||
query = append(query, "r.time IS NULL")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -937,17 +937,30 @@ func TestListRules(t *testing.T) {
|
||||
|
||||
func TestListUserRules(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM rules")
|
||||
_, err := db.Exec("DELETE FROM domains_role_actions")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean domains_role_actions unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_role_members")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_roles")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM rules")
|
||||
assert.Nil(t, err, fmt.Sprintf("clean rules unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewRepository(database)
|
||||
|
||||
domainID := generateUUID(t)
|
||||
domainRoute := generateUUID(t)
|
||||
userID := generateUUID(t)
|
||||
domainUserID := generateUUID(t)
|
||||
otherUserID := generateUUID(t)
|
||||
channelID := generateUUID(t)
|
||||
|
||||
_, err := db.Exec(`INSERT INTO domains (id, name, route, status) VALUES ($1, $2, $3, $4)`, domainID, namegen.Generate(), domainRoute, 0)
|
||||
assert.Nil(t, err, fmt.Sprintf("insert domains unexpected error: %s", err))
|
||||
|
||||
// Create 10 rules; assign the first 4 to userID via a role.
|
||||
var allRules []re.Rule
|
||||
for i := range 10 {
|
||||
@@ -977,6 +990,14 @@ func TestListUserRules(t *testing.T) {
|
||||
assert.Nil(t, err, fmt.Sprintf("insert rules_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
domainRoleID := generateUUID(t)
|
||||
_, err = db.Exec(`INSERT INTO domains_roles (id, name, entity_id) VALUES ($1, $2, $3)`, domainRoleID, "admin", domainID)
|
||||
assert.Nil(t, err, fmt.Sprintf("insert domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, domainRoleID, domainUserID, domainID)
|
||||
assert.Nil(t, err, fmt.Sprintf("insert domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_actions (role_id, action) VALUES ($1, $2)`, domainRoleID, "rule_read")
|
||||
assert.Nil(t, err, fmt.Sprintf("insert domains_role_actions unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
userID string
|
||||
@@ -1053,6 +1074,17 @@ func TestListUserRules(t *testing.T) {
|
||||
count: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user rules via domain role returns all domain rules",
|
||||
userID: domainUserID,
|
||||
pm: re.PageMeta{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Status: re.AllStatus,
|
||||
},
|
||||
count: 10,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list rules for user with no role assignments returns 0",
|
||||
userID: otherUserID,
|
||||
|
||||
@@ -452,25 +452,38 @@ func (repo *PostgresRepository) ListAllReportsConfig(ctx context.Context, pm rep
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ListUserReportsConfig(ctx context.Context, userID string, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
pq := pageReportQuery(pm)
|
||||
clauses := []string{
|
||||
`(
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM reports_roles rr
|
||||
JOIN reports_role_members rrm ON rrm.role_id = rr.id
|
||||
WHERE rr.entity_id = rc.id AND rrm.member_id = :user_id
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM domains_roles dr
|
||||
JOIN domains_role_members drm ON drm.role_id = dr.id
|
||||
JOIN domains_role_actions dra ON dra.role_id = dr.id
|
||||
WHERE dr.entity_id = rc.domain_id
|
||||
AND drm.member_id = :user_id
|
||||
AND dra.action LIKE 'report%'
|
||||
)
|
||||
)`,
|
||||
}
|
||||
clauses = append(clauses, pageReportQueryConditions(pm)...)
|
||||
orderClause := reportsOrderClause(pm)
|
||||
pgData := reportsPageData(pm)
|
||||
|
||||
pm.UserID = userID
|
||||
userJoin := `
|
||||
INNER JOIN reports_roles rr ON rr.entity_id = rc.id
|
||||
INNER JOIN reports_role_members rrm ON rrm.role_id = rr.id AND rrm.member_id = :user_id
|
||||
`
|
||||
|
||||
whereClause := pq
|
||||
whereClause := fmt.Sprintf("WHERE %s", strings.Join(clauses, " AND "))
|
||||
|
||||
innerQ := fmt.Sprintf(`
|
||||
SELECT DISTINCT rc.id, rc.name, rc.description, rc.domain_id, rc.metrics, rc.email, rc.config,
|
||||
rc.start_datetime, rc.due, rc.recurring, rc.recurring_period, rc.created_at, rc.created_by, rc.updated_at, rc.updated_by, rc.status
|
||||
FROM report_config rc
|
||||
%s
|
||||
%s
|
||||
`, userJoin, whereClause)
|
||||
`, whereClause)
|
||||
|
||||
q := fmt.Sprintf(`
|
||||
SELECT * FROM (%s) AS sub %s %s;
|
||||
@@ -637,6 +650,17 @@ func reportsPageData(pm reports.PageMeta) string {
|
||||
}
|
||||
|
||||
func pageReportQuery(pm reports.PageMeta) string {
|
||||
query := pageReportQueryConditions(pm)
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
func pageReportQueryConditions(pm reports.PageMeta) []string {
|
||||
var query []string
|
||||
if pm.Status != reports.AllStatus {
|
||||
query = append(query, "rc.status = :status")
|
||||
@@ -653,11 +677,5 @@ func pageReportQuery(pm reports.PageMeta) string {
|
||||
if pm.Name != "" {
|
||||
query = append(query, "rc.name ILIKE '%' || :name || '%'")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -485,16 +485,29 @@ func TestListReportsConfig(t *testing.T) {
|
||||
|
||||
func TestListUserReportsConfig(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
_, err := db.Exec("DELETE FROM report_config")
|
||||
_, err := db.Exec("DELETE FROM domains_role_actions")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_role_actions unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_role_members")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains_roles")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM domains")
|
||||
require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err))
|
||||
_, err = db.Exec("DELETE FROM report_config")
|
||||
require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
|
||||
})
|
||||
|
||||
repo := postgres.NewRepository(database)
|
||||
|
||||
domainID := generateUUID(t)
|
||||
domainRoute := generateUUID(t)
|
||||
userID := generateUUID(t)
|
||||
domainUserID := generateUUID(t)
|
||||
otherUserID := generateUUID(t)
|
||||
|
||||
_, err := db.Exec(`INSERT INTO domains (id, name, route, status) VALUES ($1, $2, $3, $4)`, domainID, namegen.Generate(), domainRoute, 0)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains unexpected error: %s", err))
|
||||
|
||||
num := 10
|
||||
var allCfgs []reports.ReportConfig
|
||||
for i := range num {
|
||||
@@ -521,6 +534,14 @@ func TestListUserReportsConfig(t *testing.T) {
|
||||
require.Nil(t, err, fmt.Sprintf("insert reports_role_members unexpected error: %s", err))
|
||||
}
|
||||
|
||||
domainRoleID := generateUUID(t)
|
||||
_, err = db.Exec(`INSERT INTO domains_roles (id, name, entity_id) VALUES ($1, $2, $3)`, domainRoleID, "admin", domainID)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_roles unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, domainRoleID, domainUserID, domainID)
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_role_members unexpected error: %s", err))
|
||||
_, err = db.Exec(`INSERT INTO domains_role_actions (role_id, action) VALUES ($1, $2)`, domainRoleID, "report_read")
|
||||
require.Nil(t, err, fmt.Sprintf("insert domains_role_actions unexpected error: %s", err))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
userID string
|
||||
@@ -583,6 +604,17 @@ func TestListUserReportsConfig(t *testing.T) {
|
||||
size: 0,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user reports via domain role returns all domain reports",
|
||||
userID: domainUserID,
|
||||
pageMeta: reports.PageMeta{
|
||||
Domain: domainID,
|
||||
Limit: 100,
|
||||
Offset: 0,
|
||||
},
|
||||
size: 10,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list user reports with non-existing domain returns 0",
|
||||
userID: userID,
|
||||
|
||||
Reference in New Issue
Block a user