How to dynamically inject permission checking logic for RAILS_API method - ruby-on-rails

The current project adopts the front-end and back-end separation architecture, the front-end is realized by VUE, and the back-end adopts RAILS. Now we need to solve the front-end and back-end authentication logic, and hope that the back-end will force the front-end authentication for each API to have access rights. I hope someone can help to answer, the following is some of my implementation logic:
Base class controller logic:
def collect_metrics
start_time = (Time.now.to_f.round(3) * 1000).to_i
# ap self.as_json
yield
end_time = (Time.now.to_f.round(3) * 1000).to_i
duration = end_time - start_time
# 日志转储
save_logger(duration: duration, severity: "INFO")
# 刷新用户操作时间
update_user_online
Rails.logger.debug("正在调用 API 接口:#{#api_operation},授权用户为 #{#current_user}")
end
User association permission dictionary:
u.roles = ["log:list", "error:list", "online:list", "log:edit", "log_error:del", "user:offline", "user:list", "menu:list", "job:list", "role:list", "dept:list", "dict:list", "dept:edit", "dept:del", "dept:add", "dict:edit", "dict:del", "dict:add"]
Each ACTION binds a class variable:
# 创建部门
def create
#api_operation = "创建部门"
#api_authorize = "dept:add"
pre_authorize
data = dept_params.except(:id, :isTop, :sub_count).as_json
pid = data.delete("pid")
data[:parent] = SysDept.find(pid) if pid.to_i > 0
dept = SysDept.new data
if dept.save
render json: { content: "成功创建部门" }, status: :ok
else
render json: { content: "创建部门异常" }, status: :not_acceptable
end
end
# 更新部门
def update
#api_operation = "更新部门"
#api_authorize = "dept:edit"
pre_authorize
data = dept_params.except(:label, :hasChildren, :leaf, :isTop, :sub_count)
id = data.delete("id")
if id.present? && SysDept.find(id).update!(data)
render json: { message: "更新部门成功" }, status: :ok
else
render json: { message: "更新部门异常" }, status: :not_acceptable
end
end
here is java's solution code,I don't know how to convert the following code into RUBY style。
#Log("删除部门")
#ApiOperation("删除部门")
#DeleteMapping
#PreAuthorize("#el.check('dept:del')")
public ResponseEntity<Object> deleteDept(#RequestBody Set<Long> ids){
Set<DeptDto> deptDtos = new HashSet<>();
for (Long id : ids) {
List<Dept> deptList = deptService.findByPid(id);
deptDtos.add(deptService.findById(id));
if(CollectionUtil.isNotEmpty(deptList)){
deptDtos = deptService.getDeleteDepts(deptList, deptDtos);
}
}
// 验证是否被角色或用户关联
deptService.verification(deptDtos);
deptService.delete(deptDtos);
return new ResponseEntity<>(HttpStatus.OK);
}
#Log("修改部门")
#ApiOperation("修改部门")
#PutMapping
#PreAuthorize("#el.check('dept:edit')")
public ResponseEntity<Object> updateDept(#Validated(Dept.Update.class) #RequestBody Dept resources){
deptService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

If I understand your problem, you can add a before_action :check_preauth in your controller, then implement the method check_preauth checking the user's permission to perform the action.

Related

Rspec : Declaring a variable that will be created into the method as record

I'm quite new to rails, but I seem to be facing an issue I cannot solve. I am trying to test a background job that will create a new record and use it later to instanciate a new service.
As I am testing a background job, it seems that I shoud be making a double of this service BEFORE the test, but one of the parameters is created whithin the job, using another record that is created whithin the job as a reference. Hence, there will always be one difference in terms of ID everytime I try to run it.
Here is the sample code :
RSpec.describe UpdateVideoPerfDatasJob, :type => :job do
it { expect(described_class).to be < ActiveJob::Base }
context '#perform' do
let (:split_test) { create(:split_test_for_result_slot) }
let(:adwords_account) { split_test.adwords_account }
let(:user) { split_test.adwords_account.user }
let(:report_service) { instance_double('VideoPerformanceReportBis') }
let(:api_service) { instance_double('GoogleAdsApiWrapper') }
let(:campaigns_datas) { '#2 Api called...' }
let(:segments_datas) { '#3 Api called...' }
let(:data) { '#4 Global Array Created' }
before do
allow(VideoPerformanceReportBis).to receive(:new).with(split_test: split_test, adwords_account: adwords_account, user: user, result_slot: result_slot).and_return(report_service)
allow(GoogleAdsApiWrapper).to receive(:new).with(adwords_account: adwords_account, user: user).and_return(api_service)
allow(report_service).to receive(:refresh).with(no_args).and_return('#1 Refresh method')
allow(report_service).to receive(:call_api_campaigns).with(no_args).and_return(campaigns_datas)
allow(api_service).to receive(:get_campaigns).with(no_args).and_return('...and campaign gotten !')
allow(report_service).to receive(:call_api_segments).with(no_args).and_return(segments_datas)
allow(api_service).to receive(:get_campaigns_segments).with(no_args).and_return('... and segments gotten !')
allow(report_service).to receive(:create_global_array).with(campaigns_datas, segments_datas).and_return(data)
allow(report_service).to receive(:update).with(data).and_return('#5 update data')
class UpdateVideoPerfDatasJob < ApplicationJob
def perform(split_test:, adwords_account:, user:)
logger.warn 'Updating video performance data'
history_log = HistoryLog.create(
split_test: split_test,
user: user,
adwords_account: adwords_account,
operation: 'Update vidéo performance data',
messages: '',
status: 'Initiated'
)
result_slot = ResultSlot.create(
split_test: split_test,
history_log: history_log,
)
result_slot.previous_result_slot_id = split_test.result_slot.id unless split_test.result_slot.nil?
report_service = VideoPerformanceReportBis.new(
split_test: split_test,
adwords_account: adwords_account,
user: user,
result_slot: result_slot
)
response = report_service.refresh
history_log.update!(
messages: "Data updated on #{Date.today}",
status: 'Video performance data updated'
)
NotificationsChannel.broadcast_to user, 'Finished Update Video Performance Data Worker'
end
end
As you can see, the VideoPerformanceReportService is using a result_slot as parameter, that is created inside the job, using an history_log (also created inside the job) as a reference.
How can I test this ?
(Thank you in advance for everyone helping)

Trying to decode a JWT token, however decode function returning unexpected Object and attributes are not accessible

thank you in advance for the help. I am currently attempting to decode a JWT token on my frontend react app sent from a ruby on rails restful API. I am creating the token on the back end like so:
def create
#user = User.find_by(email: session_params[:email])
if #user && #user.authenticate(session_params[:password])
token = JWT.encode(#user, ENV["JWT_SECRET"])
render json: {
email: #user.email,
name: #user.name,
discipline: #user.discipline,
id: #user.id,
token: token
}
else
render json:{
error: "User not Found",
details: #user.errors.full_messages
}, status: :unauthorized
end
end
I then handle saving it to local storage on the front end, and then attempting to decode the token like this:
export function checkLoginStatus() {
let token = window.localStorage.getItem("ST_APP");
if (token) {
let user = decode(token);
console.log(user);
return user; //returns user object
} else {
return false; //returns false if ST_APP token not found in local storage
}
}```
The problem is that console.log(user) prints #<User:0x00007f3870068ec0> and the attributes of the object are innaccessible. From all the documentation I have been looking at I would expect decode(token) to return a plain old javascript object that I could then access the attributes of. This is my first time working with JWT tokens so any advice is appreciated.

Grails loading widgets in Parallel

I have some widgets in my application. I want to load them in a single page. It works always when loaded sequentially,but fails(throws exception-java.lang.IllegalStateException-Cannot ask for request attribute - request is not active anymore!) when loaded in parallel. Am I missing something ? Is there any other way to load these in parallel? Please suggest. Here is my code snippet
class TestController {
def widget1() {
render "Widget 1"
}
def widget2() {
render "Widget 2"
}
}
class ManagerController {
def loadSequential(){
def data = [:]
data['view1'] = g.include(action: 'widget1', controller: 'test')
data['view2'] = g.include(action: 'widget2', controller: 'test')
render data['view1']
render data['view2']
}
def loadParallel(){
def data = [:]
PromiseMap pm = new PromiseMap()
pm['view1'] = { g.include(action: 'widget1', controller: 'test') }
pm['view2'] = { g.include(action: 'widget2', controller: 'test') }
def result = pm.get()
render result['view1']
render result['view2']
}
}
Here's my 2p on this matter.
We've tried really hard to make Grails render pages in parallel but we couldn't make it work. After digging really deep we simply found that Grails is not ready for this kind of stuff. It's build in a way where rendering is always thread-exclusive.
I'd say it's all due to the org.codehaus.groovy.grails.web.pages.GroovyPageOutputStack that is not ready for a concurrent access.
I'll watch this space just in case someone managed to make it work.
Try this:
import static grails.async.Promises.*
...
def loadParallel() {
task {
def data = [:]
PromiseMap pm = new PromiseMap()
pm['view1'] = { g.include(action: 'widget1', controller: 'test') }
pm['view2'] = { g.include(action: 'widget2', controller: 'test') }
def result = pm.get()
render result['view1'] + result['view2']
}
}
In the loadSequential() example you are not loading response sequentially, but concatenating two views into a single response. Parallel rendering should be a client side job. See for example how Lift does it with jQuery.

Grails domain class saves a NULL id property; leading to error when running web app

In this project I am attempting to save data returned by Yahoo Finance URL API. The URL to be accessed is "http://ichart.yahoo.com/table.csv?s=GOOG&a=0&b=1&c=2000&d=0&e=31&f=2010&g=w&ignore=.csv". I attempt to save the returned data as a String in my StockQuote.stockInfo parameter.
I get the following error with saving a StockQuote instance object:
Error 500: Internal Server Error
URI: /StockScreenerSD/stockQuote/tickerLookup
Class: org.h2.jdbc.JdbcSQLException
Message: NULL not allowed for column "STOCK_QUOTE"; SQL statement: insert into stock_quote (id, version, date_created, stock_info, ticker) values (null, ?, ?, ?, ?) [23502-164]
Around line 26 of grails-app/controllers/org/grails/finance/StockQuoteController.groovy
23:// def url = ("http://download.finance.yahoo.com/d/quotes.csv?s=" + stockQuote.ticker + "&f=xsna2t8pj1a").toURL()
24: def url = ("http://ichart.yahoo.com/table.csv?s=" +stockQuote.ticker+ "&a=0&b=1&c=2000&d=0&e=31&f=2010&g=w&ignore=.csv").toURL()
25: stockQuote.stockInfo = url.text.toString()
26: stockQuote.save()(flush: true)
27: def stockQuoteList = stockQuoteList()
28: render template: 'usedTickers', collection: stockQuoteList, var: 'stockData'
29: }
My Controller Code which attempts the saving action is as below:
package org.grails.finance
import grails.plugins.springsecurity.Secured
#Secured('IS_AUTHENTICATED_FULLY')
class StockQuoteController {
// def scaffold = true
def index() {
redirect (action:'getTicker')
}
def getTicker() {
def listing = stockQuoteList()
return [stockQuoteList: listing] // this is a map. key=>value
}
def tickerLookup = {
def stockQuote = new StockQuote(ticker: params.ticker)
def url = ("http://ichart.yahoo.com/table.csv?s=" +stockQuote.ticker+ "&a=0&b=1&c=2000&d=0&e=31&f=2010&g=w&ignore=.csv").toURL()
stockQuote.stockInfo = url.text.toString()
stockQuote.save()(flush: true)
def stockQuoteList = stockQuoteList()
render template: 'usedTickers', collection: stockQuoteList, var: 'stockData'
}
private stockQuoteList() {
StockQuote.list()
}
I hope this should be my last edit. Proof that friday becomes unproductive if you had a long busy week. :) Ok, here is how the setup has to be, it works for me:
//Domain class StockQuote
class StockQuote {
String ticker
String stockInfo
static mapping = {
stockInfo type: "text" //You have to set the type since the info is CLOB
}
}
//Controller Action
def tickerLookup = {
def stockQuote = new StockQuote(ticker: params.ticker)
def url = ("http://ichart.yahoo.com/table.csv?s=" +stockQuote.ticker+ "&a=0&b=1&c=2000&d=0&e=31&f=2010&g=w&ignore=.csv").toURL()
stockQuote.stockInfo = url.text.toString()
stockQuote.save(flush: true) //Look at how flush is used in save
def stockQuoteList = stockQuoteList()
render template: 'usedTickers', collection: stockQuoteList, var: 'stockData'
}
This above works for me and I am able to save the large CLOB response from the url to the table. Have a look at my test.

webflow in grails application

how can i redirect to the same state more than one time using web flow
ex:
on('submit'){
def destinationInstance = Destination.get(params.destination)
def destinationGroupsInstance = DestinationGroup.get(params.destinationGroups)
def h = destinationInstance.addToDestinationGroups(destinationGroupsInstance)
}.to('flowList')
what i need is how to enter to this state more than one time until destinations ends
thx
on('submit'){
def destinationInstance = Destination.get(params.destination)
def destinationGroupsInstance = DestinationGroup.get(params.destinationGroups)
def h = destinationInstance.addToDestinationGroups(destinationGroupsInstance)
}.to{
(condition or while loop or for loop)
if success then
return "<state name>"
else
return "flowList"
}
Reference:
http://livesnippets.cloudfoundry.com/docs/guide/2.%20Grails%20webflow%20plugin.html
Well, you'd probably have something like the following code, which is untested but may give you a general idea.
def destinationFlow = {
initialize {
action {
flow.destination = Destination.get(params.id)
}
on('success').to 'destinationList'
}
destinationList {
render(view: 'destinationList')
on('addDestination') {
def destinationGroup = DestinationGroup.get(params.destinationGroupId)
flow.destination.addToDestinationGroups(destinationGroup)
}.to 'destinationList'
on('finish').to 'done'
}
done {
flow.destination.save()
redirect(...) // out of the flow
}
}
You'll need buttons on your destinationList view that invoke the 'addDestination' or 'finish' actions. See the WebFlow documentation and Reference Guide.

Resources