Jenkins Extensible Choice with user specific items based on users Roles - jenkins

I have a situation where I would like to alter the contents of a choice parameter in a Jenkins parametrised build.
In my case I would like one project for deploying the application 'Deploy My App'. When building this project the user is presented with a choice parameter. I would like to alter the contents of this list depending on a user role. i.e. someone with the 'dev_deploy' role will be able to see the dev environments, someone with the 'test_deploy' role will be able to see the test environments etc.
I am currently using the Extensible Choice Parameter plugin and the Role-based Authorization Strategy plugin.
I know that I can write some groovey script to generate the list items for the choice.
def result = ["-------"]
def roles=??????
if(roles.get('dev_deploy') {
//Add dev environments
result.add('dev1')
....
}
if(roles.get('test_deploy') {
//Add test environments
result.add('test1')
....
}
return result
I just can't figure out who to get hold of the users roles?
Anyone know how I might do this, or have different solution to the problem?
Many thanks

OK, after a few more searches I came across the source (https://github.com/jenkinsci/role-strategy-plugin/tree/master/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy)
After further reading and a bit of playing around I came up with this...
import com.michelin.cio.hudson.plugins.rolestrategy.*
def result = ["-- Please Select --"]
def authStrategy = jenkins.model.Jenkins.instance.getAuthorizationStrategy()
if(authStrategy instanceof RoleBasedAuthorizationStrategy){
def currentUser = jenkins.model.Jenkins.instance.getAuthentication().getName();
def roleMap= authStrategy.roleMaps.get("globalRoles")
def sids= roleMap.getSidsForRole("Manage_Dev")
if(sids != null && sids.contains(currentUser)) {
result.add("dev1")
...
}
sids= roleMap.getSidsForRole("Manage_Test")
if(sids != null && sids.contains(currentUser)) {
result.add("tst1")
...
}
...
}
return result
Which works for me. Easy when you know how!

I came across code that helps me to overcome the same problem. I have refined the choice by using the Active Choice plugin and the Jenkins user role. Admins will have different drop-down options and a tester will have different drop-down options.
https://kinoshita.eti.br/2016/04/24/using-active-choices-with-role-strategy-plugin.html
import hudson.model.User
import hudson.model.Hudson
import hudson.security.AuthorizationStrategy
import hudson.security.Permission
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy
import com.michelin.cio.hudson.plugins.rolestrategy.RoleMap
AuthorizationStrategy strategy = Hudson.getInstance().getAuthorizationStrategy();
jobs = []
user = User.current()
userId = user.getId()
if (strategy != null && strategy instanceof com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy) {
roleStrategy = (RoleBasedAuthorizationStrategy) strategy;
// not very straightforward to get the groups for a given user
roles = roleStrategy.getGrantedRoles("globalRoles")
for (entry in roles) {
role = entry.key
users = entry.value
if (role.getName().equals("tester")) {
if (userId in users) {
jobs = ["PROJECT_FOR_TESTERS1", "PROJECT_FOR_TESTERS2"]
break
}
} else if (role.getName().equals("admin")) {
if (userId in users) {
jobs = ["PROJECT_FOR_ADMINS1", "PROJECT_FOR_ADMINS2"]
break
}
}
}
}
return jobs

I had to make a slight adjustment to the accepted answer to get it to work.
See Cannot invoke method getSidsForRole() on null object
import com.michelin.cio.hudson.plugins.rolestrategy.*
def result = ['-- Please Select --']
def authStrategy = jenkins.model.Jenkins.instance.getAuthorizationStrategy()
if(authStrategy instanceof RoleBasedAuthorizationStrategy){
def currentUser = jenkins.model.Jenkins.instance.getAuthentication().getName();
def roleMap = authStrategy.getRoleMap(com.synopsys.arc.jenkins.plugins.rolestrategy.RoleType.Global)
def sids = roleMap.getSidsForRole("develop")
if(sids != null && sids.contains(currentUser)) {
result.add('staging')
}
sids = roleMap.getSidsForRole("admin")
if(sids != null && sids.contains(currentUser)) {
result.add('prod')
}
}
return result

Related

grails 2.4.4 issue inverting filters schema

In a new project based on grails 2.4.4 I am using filter schema with invert option. Few controllers and some actions from another controllers are excluded in filters by inverting the rule.
Filter will not be applied to Login, ForgotPassword ans ServerError Controllers and saveUser, verifyRegistration actions from different user controller. This filter schema doesn't work as expected.
When I am calling login api inside login controller, filter is getting executed and throws exception.
package com.project.filters
import grails.converters.JSON
class MyProjectAuthorizationFilters {
def userService
def grailsApplication
def filters = {
checkXAuthToken(controller:'login|forgotPassword|serverError', action:'saveUser|verifyRegistration', invert: true) {
before = {
try{
String tokenValue = request.getHeader('X-Auth-Token')
if(tokenValue == null && tokenValue == ""){
throw new MyCustomException(401, "Please provide X-Auth-Token in Header")
}
userService.getUserByAuthToken(tokenValue)
}catch (MyCustomException error) {
error.stackTrace = ""
response.setStatus(error.status)
render error as JSON
return false
}
}
}
}
}
I know we can also use controllerExclude, actionExclude, but did not know why this is breaking?
EDIT
I even tried using controllerExclude and actionExclude but it doesn't work as expected. Is this a weired behaviour or I am doing something wrong. Posting whole filter class code.
Thanks.

What is the best way to process a large list of Domain Objects?

What is the best way to process a large list of Domain Objects?
For example, I have 'Users' and 'Book' domains, and there is a permission READ on Book object.
When I'm adding a new Book, I'd like to set READ permission to all users for this Book.
At first was a code:
def users = Users.findAll{ ... }
users.each { addPermission(book, it, READ) }
I'm using Spring Security Core and ACL plugin.
But now, I think it is not best way to load 10000 Users Objects to memory.
I gonna use the SCROLL method with maxResults(???) from Criteria.
So my question is What the best way? How to determinate the best number of MaxResults?
For something like this, I would do a bulk update. ExecuteUpdate allows you to do such a thing and its much more performant. Look at this example and customize it to your need.
def updatedRecords = User.executeUpdate("update User set permission = 'READ' where somecriteriaOrNot ")
A more of Grails way to do this would be to use the batch processing. Try the example given below:
EDIT : Improved answer. Now, using pagination based batch processing.
def noOfObjectsTobeProcessedAtAtime=1000//Step or pagination size...
List offsetMaxMapList = (0..User.count()-1).step(noOfObjectsTobeProcessedAtAtime).collect{[max:noOfObjectsTobeProcessedAtAtime,offset:it]}
offsetMaxMapList.each{offsetMaxMap->
addPermissionToUserInBatch(params)
}
def addPermissionToUserInBatch(params){
def batch = []
def session
def users = Users.createCriteria().list(params){}
users.eachWithIndex { user, index ->
batch << user
if (batch.size() >= batchSize) {
User.withTransaction {
batch.each {User userObject ->
addPermission(book, userObject, READ)
}
}
batch.clear()
} else if (batch.size() < batchSize && (users.size() - index - 1) == 0) {
User.withTransaction {
batch.each {User userObject ->
addPermission(book, userObject, READ)
}
}
batch.clear()
}
session = sessionFactory.getCurrentSession()
session.clear()
}
}
Hope that helps!!!
Thank you all. I'd like to summarize. I hope ti will be a TEMPLATE to me.
def dc = new DetachedCriteria(Users).build{
//some conditions of criteria
}
def count = dc.count()
// Optional:
// dc = dc.build{
// projections { property('username') }
// }
def batchSize = 50 // Hibernate Doc recommends 10..50
0.step(count, batchSize){ offset->
dc.list(offset:offset, max:batchSize).each{
// doSmthWithTransaction(it)
}
//clear the first-level cache
//def hiberSession = sessionFactory.getCurrentSession()
//hiberSession.clear()
// or
Users.withSession { session -> session.clear() }
}
P.S. I don't use Transaction here since I use it on the doSmthWithTransaction method

Filtering filtered data

I'm new to grails and MVC so please bear with me.
I have some links on my GSP that do some static filtering. For instance, the example below returns only
those Request domain class instances with status Open. But I also want to be able to do some dynamic filtering on the same model (results in the code bellow).
Use case would be something like this: User sees all Request domain class instances in the table. He clicks on the link Open requests and gets only those Request instances that have status property with value Open. Than he sets dateFrom and dateTo using date picker control and clicks on the Filter button which calls the method/action that further filters data from the table. So it should return only those request that are opened and that are created within the specified period.
def openedRequests = {
def contact = Contact?.findByUser(springSecurityService.currentUser)
def productlines = contact.productlines()
def requestCriteria = Request.createCriteria()
def results = requestCriteria.list {
eq("status", "Open")
and {
'in'("productline",productlines)
}
}
render(view:'supportList', model:[requestInstanceList:results, requestInstanceTotal: results.totalCount])
}
EDIT
On my GSP I have few links that call controller actions which perform some domain class instances filtering. For example I have OpenedRequests, ClosedRequests, NewRequests. But I also have some textboxes, comboboxes, datePicker controls for additional filtering. I call the filterRequests action with a button.
def filterRequests = {
def contact = Contact?.findByUser(springSecurityService.currentUser)
def productlines = contact.productlines()
def requestCriteria = Request.createCriteria()
def results = requestCriteria.list {
if(params.fDateFrom && params.fDateTo){
def dateFrom = new SimpleDateFormat("dd.MM.yyyy").parse(params.fDateFrom_value)
def dateTo = new SimpleDateFormat("dd.MM.yyyy").parse(params.fDateTo_value)
between("dateCreated",dateFrom,dateTo)
}
if(params?.fStatus){
eq("status",params.fStatus)
}
if(params?.fCompany){
eq("company", params.fCompany)
}
and {'in'("productline",productlines)
}
if(params.sort != null && params.order != null){
order(params.sort, params.order)
}
}
render(view:'supportList', model:[requestInstanceList:results, requestInstanceTotal: results.totalCount])
}
I want to be able to filter Request instances with some of mentioned links and than if I set up some additional filters, for example dateFrom i dateTo with datePicker. I want those filters to be aware of previous filtering with link if there were any. What is the right way to do this?
You can use DetachedCriterias which where introduced with Grails 2.0.
A DetachedCriteria is independed from any session and can be reused easily:
def openRequests = new DetachedCriteria(Request).build {
eq("status", "Open")
and {
'in'("productline",productlines)
}
}
Then upon your next sub-filter request you can reuse the DetachedCriteria and perform a sub-query on it, like:
def results = openRequests.findByStartDateBetweenAndEndDateBetween(dateFrom, dateTo, dateFrom, dateTo)
Of course you have to remember somehow what the original query was (session, request param), to use the correct criteria as a basis for the sub-query.
(Disclaimer: I haven't yet tried detached criterias myself)
David suggested that I use Detached Criteria but I am using Grails 1.3.7 for my app. So, at the moment this isn't an option. I also thought of using database views and stored procedures but I wasn't sure how that will work with Grails (but that is something that I will definitely have to explore) and I wanted some results fast so I did something not very DRY. When I filter table with one of the mentioned links I save the name of the link/action in session and in filterRequest action (that does additional filtering) I check the session to see if there has been any previous 'link filtering' and if it were I apply those filters on the table with criteria, and after that I apply the filters that were manualy entered. I don't like it but that's all I came up with with my limited understanding of Grails. Below is my filterRequest action:
def filterRequests = {
def contact = Contact?.findByUser(springSecurityService.currentUser)
def productlines = contact.productlines()
def requestCriteria = Request.createCriteria()
def results = requestCriteria.list {
if(session.filter == "newRequests"){
and{
isNull("acceptedBy")
ne("status", "Closed")
}
}
if(session.filter == "openRequests"){
and{
ne("status",'Closed')
}
}
if(session.filter == "closedRequests"){
and{
eq("status", "Closed")
}
}
if(session.filter == "myRequests"){
and{
eq("acceptedBy", contact.realname)
}
}
if(params.fDateFrom && params.fDateTo){
def dateFrom = new SimpleDateFormat("dd.MM.yyyy").parse(params.fDateFrom_value)
def dateTo = new SimpleDateFormat("dd.MM.yyyy").parse(params.fDateTo_value)
and{
between("dateCreated",dateFrom,dateTo)
}
}
if(params?.fAcceptedBy){
and{
eq("acceptedBy", params.fAcceptedBy)
}
}
if(params?.fStartedBy){
and{
eq("startedBy", params.fStartedBy)
}
}
if(params?.fCompany){
and{
ilike("company", "%" + params.fCompany +"%")
}
}
and {'in'("productline",productlines)
}
if(params.sort != null && params.order != null){
order(params.sort, params.order)
}
}
}

Grails geo - location initializer

We are developing our project in grails. And we want to show data to user according to the country from where they are accessing our website.
I have a field where I store the country location. By using geoip grails plugin.
My question is can we initialize the session with the country name from where the site is being excessed before it hits any controller/action, let say in some config file or somewhere else.
You should be able to do it in a Filter. Something like this, placed in grails-app/conf as GeoFilters.groovy:
class GeoFilters {
def geoIpService
def filters = {
countryCheck(controller:'*', action:'*') {
before = {
if( !session.geolocation ) {
session.geolocation = geoIpService.getLocation( request.remoteAddr )
}
}
}
}
}
Should (I haven't tried it though) check to see if geolocation exists in the session, and if it doesn't, it should fetch it from the geoIpService.

Adding a variable to all views in grails

I am trying to set a variable for the current user (a POJO) in all views so I can get things like the user name and check their role on every view (including the default layout). How can I setup something (e.g. currentUser) in grails so that it is accessible in every grails view like so:
<div>${currentUser.name}</div>
or like this:
<g:if test="${currentUser.admin}">ADMIN</g:if>
You want to use a grails filter. Using a filter, you can specify which controllers and methods (using wild cards) you want to intercept using before/after and afterView methods.
This makes it easy to stuff a new variable into the model so it's available in a view. Here's an example that uses the acegi plugin authenticateService:
class SecurityFilters {
def authenticateService
def filters = {
all(controller:'*', action:'*') {
after = { model ->
def principal = authenticateService.principal()
if (principal != null && principal != 'anonymousUser') {
model?.loggedInUser = principal?.domainClass
log.debug("SecurityFilter: adding current user to model = $model")
} else {
log.debug("SecurityFilter: anonymous user, model = $model")
}
}
}
}
}
You can use the session scope to store the variable. Your calls would change to:
<div>${session.currentUser.name}</div>
and
<g:if test="${session.currentUser.admin}">ADMIN</g:if>
And you would set the variable like so in a controller:
session.currentUser = XXXXX

Resources