Rails: Action Cable: 如何根据角色授权用户连接特定频道?
Rails: Action Cable: How to authorize user to connect specific channel based on role?
在我的 rails 拍卖应用程序中,授权用户可以在产品页面内同时连接到 2 个渠道(一个是 all_users 产品渠道,另一个是用户特定的直接消息渠道。 )
现在我只想将敏感信息发送给管理员组用户。我想我可以在咖啡脚本中定义第三个通道连接请求 (admin_channel),但我不知道如何根据角色为第三个通道授权用户连接。
另一种选择可能是利用现有的用户特定频道,但在这里我无法弄清楚后端 类 如何知道管理员组中的哪些用户当前在线(有一个用户频道 up&运行)..
你知道我该如何实现吗?任何形式的支持将不胜感激..
您可以在下面找到我现有的 connection.rb 文件和 coffeescript 文件。
这是我的 connection.rb 文件:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user # this checks whether a user is authenticated with devise
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
咖啡脚本:
$( document ).ready ->
App.myauction = App.cable.subscriptions.create({
channel: 'MyauctionChannel'
id: $('#auctionID').attr('data-id')
},
connected: ->
console.log "Connected"
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
speak: (message) ->
@perform 'speak', message: message
received: (data) ->
console.log(data)
# Called when there's incoming data on the websocket for this channel
)
App.myauctionuser = App.cable.subscriptions.create({
channel: 'MyauctionChannel',
id: $('#auctionID').attr('data-uuid-code')
},
connected: ->
console.log "user connected"
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
speak: (message) ->
@perform 'speak', message: message
received: (data) ->
# console.log ("user channel ")
# console.log(data)
)
$(document).ready ->
App.privateAdminMesssagesChannel = App.cable.subscriptions.create({
channel: 'PrivateAdminMessagesChannel'
},
connected: ->
disconnected: ->
// call this function to send a message from a Non-Admin to All Admins
sendMessageToAdmins: (message) ->
@perform 'send_messsage_to_admins', message: message
// call this function to send a messsage from an Admin to (a Non-admin + all Admins)
sendMessageToUserAndAdmins: (message, toUserId) ->
@perform 'send_messsage_to_user_and_admins', message: message, to_user_id: toUserId
received: (data) ->
console.log(data.from_user_id)
console.log(data.to_user_id)
console.log(data.message)
if data.to_user_id
// this means the message was sent from an Admin to (a Non-admin + all admins)
else
// this means the message was sent from a Non-admin to All Admins
// do some logic here i.e. if current user is an admin, open up one Chatbox
// on the page for each unique `from_user_id`, and put data.message
// in that box accordingly
)
private_admin_messages_channel.rb
class PrivateAdminMessagesChannel < ActionCable::Channel::Base
def subscribed
stream_from :private_admin_messages_channel, coder: ActiveSupport::JSON do |data|
from_user = User.find(data.fetch('from_user_id'))
to_user = User.find(data['to_user_id']) if data['to_user_id']
message = data.fetch('message')
# authorize if "message" is sent to you (a non-admin), and also
# authorize if "message" is sent to you (an admin)
if (to_user && to_user == current_user) || (!to_user && current_user.is_admin?)
# now, finally send the Hash data below and transmit it to the client to be received in the JS-side "received(data)" callback
transmit(
from_user_id: from_user.id,
to_user_id: to_user&.id,
message: message
)
end
end
end
def send_message_to_admins(data)
ActionCable.server.broadcast 'private_admin_messages_channel',
from_user_id: current_user.id,
message: data.fetch('message')
end
def send_message_to_user_and_admins(data)
from_user = current_user
reject unless from_user.is_admin?
ActionCable.server.broadcast 'private_admin_messages_channel',
from_user_id: from_user.id,
to_user_id: data.fetch('to_user_id'),
message: data.fetch('message')
end
end
以上是我能想到的最简单的方法。不是最有效的,因为每个流都发生了额外级别的授权(请参阅 stream_from
块内部),这与我们有不同的广播名称不同,后者的授权只会在 "connecting" 本身上发生一次,而不是每个 "streaming"... 都可以通过以下方式完成:
- 管理员 User1 打开页面然后 JS 订阅
UserConnectedChannel
- 非管理员 User2 打开页面然后 JS 订阅
PrivateAdminMessagesChannel
传入数据:user_id: CURRENT_USER_ID
- 来自上面的2,因为User2刚刚订阅;然后在后端,在连接后
def subscribed
内,你 ActionCable.server.broadcast :user_connected, { user_id: current_user.id }
- Admin User1 正在订阅
UserConnectedChannel
然后接收 data { user_id: THAT_USER2_id }
- 从上面的 4 开始,在 JS
received(data)
回调中,您现在 JS 订阅 PrivateAdminMessagesChannel
传递数据:THAT_USER2_id`.
- 现在User1和User2都订阅了
PrivateAdminMessagesChannel user_id: THAT_USER2_id
,这意味着他们可以私下交谈(其他管理员应该也收到了:user_connected
的JS数据:{ user_id: THAT_USER2_ID }
,所以他们也应该被订阅,因为 AdminUser1、NonAdminUser2 和 AdminUser3 可以在同一个聊天频道中交谈是有道理的……从我得到的你的要求)
- TODO:从上面的 1 到 6,对 "disconnection" 过程也做类似的事情
琐事:
- 您在
ApplicationCable::Connection
中使用 identified_by
定义的内容可以在您的频道文件中访问。特别是,在这种情况下,可以调用current_user
。
- 关于拒绝订阅,请参阅docs here
在我的 rails 拍卖应用程序中,授权用户可以在产品页面内同时连接到 2 个渠道(一个是 all_users 产品渠道,另一个是用户特定的直接消息渠道。 )
现在我只想将敏感信息发送给管理员组用户。我想我可以在咖啡脚本中定义第三个通道连接请求 (admin_channel),但我不知道如何根据角色为第三个通道授权用户连接。
另一种选择可能是利用现有的用户特定频道,但在这里我无法弄清楚后端 类 如何知道管理员组中的哪些用户当前在线(有一个用户频道 up&运行)..
你知道我该如何实现吗?任何形式的支持将不胜感激..
您可以在下面找到我现有的 connection.rb 文件和 coffeescript 文件。
这是我的 connection.rb 文件:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user # this checks whether a user is authenticated with devise
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
咖啡脚本:
$( document ).ready ->
App.myauction = App.cable.subscriptions.create({
channel: 'MyauctionChannel'
id: $('#auctionID').attr('data-id')
},
connected: ->
console.log "Connected"
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
speak: (message) ->
@perform 'speak', message: message
received: (data) ->
console.log(data)
# Called when there's incoming data on the websocket for this channel
)
App.myauctionuser = App.cable.subscriptions.create({
channel: 'MyauctionChannel',
id: $('#auctionID').attr('data-uuid-code')
},
connected: ->
console.log "user connected"
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
speak: (message) ->
@perform 'speak', message: message
received: (data) ->
# console.log ("user channel ")
# console.log(data)
)
$(document).ready ->
App.privateAdminMesssagesChannel = App.cable.subscriptions.create({
channel: 'PrivateAdminMessagesChannel'
},
connected: ->
disconnected: ->
// call this function to send a message from a Non-Admin to All Admins
sendMessageToAdmins: (message) ->
@perform 'send_messsage_to_admins', message: message
// call this function to send a messsage from an Admin to (a Non-admin + all Admins)
sendMessageToUserAndAdmins: (message, toUserId) ->
@perform 'send_messsage_to_user_and_admins', message: message, to_user_id: toUserId
received: (data) ->
console.log(data.from_user_id)
console.log(data.to_user_id)
console.log(data.message)
if data.to_user_id
// this means the message was sent from an Admin to (a Non-admin + all admins)
else
// this means the message was sent from a Non-admin to All Admins
// do some logic here i.e. if current user is an admin, open up one Chatbox
// on the page for each unique `from_user_id`, and put data.message
// in that box accordingly
)
private_admin_messages_channel.rb
class PrivateAdminMessagesChannel < ActionCable::Channel::Base
def subscribed
stream_from :private_admin_messages_channel, coder: ActiveSupport::JSON do |data|
from_user = User.find(data.fetch('from_user_id'))
to_user = User.find(data['to_user_id']) if data['to_user_id']
message = data.fetch('message')
# authorize if "message" is sent to you (a non-admin), and also
# authorize if "message" is sent to you (an admin)
if (to_user && to_user == current_user) || (!to_user && current_user.is_admin?)
# now, finally send the Hash data below and transmit it to the client to be received in the JS-side "received(data)" callback
transmit(
from_user_id: from_user.id,
to_user_id: to_user&.id,
message: message
)
end
end
end
def send_message_to_admins(data)
ActionCable.server.broadcast 'private_admin_messages_channel',
from_user_id: current_user.id,
message: data.fetch('message')
end
def send_message_to_user_and_admins(data)
from_user = current_user
reject unless from_user.is_admin?
ActionCable.server.broadcast 'private_admin_messages_channel',
from_user_id: from_user.id,
to_user_id: data.fetch('to_user_id'),
message: data.fetch('message')
end
end
以上是我能想到的最简单的方法。不是最有效的,因为每个流都发生了额外级别的授权(请参阅 stream_from
块内部),这与我们有不同的广播名称不同,后者的授权只会在 "connecting" 本身上发生一次,而不是每个 "streaming"... 都可以通过以下方式完成:
- 管理员 User1 打开页面然后 JS 订阅
UserConnectedChannel
- 非管理员 User2 打开页面然后 JS 订阅
PrivateAdminMessagesChannel
传入数据:user_id: CURRENT_USER_ID
- 来自上面的2,因为User2刚刚订阅;然后在后端,在连接后
def subscribed
内,你ActionCable.server.broadcast :user_connected, { user_id: current_user.id }
- Admin User1 正在订阅
UserConnectedChannel
然后接收data { user_id: THAT_USER2_id }
- 从上面的 4 开始,在 JS
received(data)
回调中,您现在 JS 订阅PrivateAdminMessagesChannel
传递数据:THAT_USER2_id`. - 现在User1和User2都订阅了
PrivateAdminMessagesChannel user_id: THAT_USER2_id
,这意味着他们可以私下交谈(其他管理员应该也收到了:user_connected
的JS数据:{ user_id: THAT_USER2_ID }
,所以他们也应该被订阅,因为 AdminUser1、NonAdminUser2 和 AdminUser3 可以在同一个聊天频道中交谈是有道理的……从我得到的你的要求) - TODO:从上面的 1 到 6,对 "disconnection" 过程也做类似的事情
琐事:
- 您在
ApplicationCable::Connection
中使用identified_by
定义的内容可以在您的频道文件中访问。特别是,在这种情况下,可以调用current_user
。 - 关于拒绝订阅,请参阅docs here