Possible bug in Rails 4 Tutorial, section 9.6, exercise 1 - railstutorial.org

I'm working through the Rails 4 version of Michael Hartl's Rails Tutorial and having trouble with section 9.6 exercise 1 (Listing 9.49).
It looks like the test in the tutorial passes for the wrong reason. Before the PATCH request, user.admin? is false by default; after the PATCH request user.admin? is still false (thus passing the test) because the PATCH request is not getting to the UsersController#update method.
Here's my code:
spec/requests/user_pages_spec.rb (other tests removed to isolate the one in question):
require 'spec_helper'
describe "User pages" do
subject { page }
describe 'edit' do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
visit edit_user_path(user)
end
describe "forbidden attributes" do
let(:params) do
{ user: { name: 'Forbidden Attributes',
password: user.password,
password_confirmation: user.password,
admin: true } }
end
before { patch user_path(user), params }
specify { expect(user.reload).not_to be_admin }
end
end
end
Relevant parts of app/controllers/users_controller.rb:
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update]
before_action :correct_user, only: [:edit, :update]
# PATCH /users/:id
def update
# #user is set in before_action
if #user.update_attributes(user_params)
# handle a successful update
flash[:success] = 'Profile updated'
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation, **:admin**)
end
# Before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: 'Please sign in.'
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
spec/factories.rb:
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:email) { |n| "person_#{n}#example.com" }
password "foobar"
password_confirmation "foobar"
factory :admin do
admin true
end
end
end
And here's what the test log shows happens:
Started PATCH "/users/2111" for 127.0.0.1 at 2013-08-18 21:30:44 -0400
Processing by UsersController#update as HTML
Parameters: {"user"=>{"name"=>"Forbidden Attributes", "password"=>"[FILTERED]", \
"password_confirmation"=>"[FILTERED]", "admin"=>"true"}, "id"=>"2111"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = \
'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
**Redirected to http://www.example.com/signin
Filter chain halted as :signed_in_user rendered or redirected**
Completed 302 Found in 2ms (ActiveRecord: 0.4ms)
I downloaded the reference version of the code from https://github.com/railstutorial/sample_app_rails_4 and ran rspec spec/requests/user_pages_spec.rb. The test log showed the same thing: the PATCH request is being stopped by signed_in_user and never getting to the update method.
When I played around with testing that admin IS set and added some puts statements, it looked like the user that is being signed in is not the same user that is being tested; the user.id stays constant, but the user.name changes. I wonder if it has something to do with the sequence() calls in the factory.
Can anyone verify or refute my findings?
How can I write this test properly?
Found solution
Further investigation seemed to implicate the remember_token. If I move the "forbidden attributes" test out of the "edit" block and add "capybara: true" to the sign_in call, it works. So Listing 9.49 (spec/requests/user_pages_spec.rb) should look like this:
require 'spec_helper'
describe "User pages" do
subject { page }
.
.
.
describe "update forbidden attributes" do
let(:user) { FactoryGirl.create(:user) }
let(:params) do
{ user: { admin: true, password: user.password,
password_confirmation: user.password } }
end
before do
sign_in user, no_capybara: true
patch user_path(user), params
end
specify { expect(user.reload).not_to be_admin }
end
end

I just wanted to confirm that I'm seeing the same behavior when the "forbidden attributes" test is nested within the "edit" test block.
My notes indicate that it was mentioned in Chapter 9 that when ever you're doing a direct POST, PATCH, GET or DELETE request, as opposed to using visit, the no_capybara: true option needs to be given to the sign_in method to ensure the user is signed in.
However, in this case if you use the no_capybara: true option with sign_in the other tests in the "edit" block will fail due to some Capybara issues.
As mentioned by the OP, if the option is omitted, then the "forbidden attributes" tests pass regardless of the presence, or lack thereof, of :admin in the user_params method within the Users controller.

Same problem here. With metafour's help, I find the following works. We need to sign in the user with capybara: true for patch to work.
describe "forbidden attributes" do
let(:params) do
{ user: { admin: true, password: user.password,
password_confirmation: user.password } }
end
before do
sign_in user, no_capybara: true
patch user_path(user), params
end
specify { expect(user.reload).not_to be_admin }
end

Related

Jenkins dynamic choice parameter to read a ansible host file in github

I have an ansible host file that is stored in GitHub and was wondering if there is a way to list out all the host in jenkins with choice parameters? Right now every time I update the host file in Github I would have to manually go into each Jenkins job and update the choice parameter manually. Thanks!
I'm assuming your host file has content something similar to below.
[client-app]
client-app-preprod-01.aws-xxxx
client-app-preprod-02.aws
client-app-preprod-03.aws
client-app-preprod-04.aws
[server-app]
server-app-preprod-01.aws
server-app-preprod-02.aws
server-app-preprod-03.aws
server-app-preprod-04.aws
Option 01
You can do something like the one below. Here you can first checkout the repo and then ask for the user input. I have implemented the function getHostList() to parse the host file to filter the host entries.
pipeline {
agent any
stages {
stage('Build') {
steps {
git 'https://github.com/jglick/simple-maven-project-with-tests.git'
script {
def selectedHost = input message: 'Please select the host', ok: 'Next',
parameters: [
choice(name: 'PRODUCT', choices: getHostList("client-app","ansible/host/location"), description: 'Please select the host')]
echo "Host:::: $selectedHost"
}
}
}
}
}
def getHostList(def appName, def filePath) {
def hosts = []
def content = readFile(file: filePath)
def startCollect = false
for(def line : content.split('\n')) {
if(line.contains("["+ appName +"]")){ // This is a starting point of host entries
startCollect = true
continue
} else if(startCollect) {
if(!line.allWhitespace && !line.contains('[')){
hosts.add(line.trim())
} else {
break
}
}
}
return hosts
}
Option 2
If you want to do this without checking out the source and with Job Parameters. You can do something like the one below using the Active Choice Parameter plugin. If your repository is private, you need to figure out a way to generate an access token to access the Raw GitHub link.
properties([
parameters([
[$class: 'ChoiceParameter',
choiceType: 'PT_SINGLE_SELECT',
description: 'Select the Host',
name: 'Host',
script: [
$class: 'GroovyScript',
fallbackScript: [
classpath: [],
sandbox: false,
script:
'return [\'Could not get Host\']'
],
script: [
classpath: [],
sandbox: false,
script:
'''
def appName = "client-app"
def content = new URL ("https://raw.githubusercontent.com/xxx/sample/main/testdir/hosts").getText()
def hosts = []
def startCollect = false
for(def line : content.split("\\n")) {
if(line.contains("["+ appName +"]")){ // This is a starting point of host entries
startCollect = true
continue
} else if(startCollect) {
if(!line.allWhitespace && !line.contains("[")){
hosts.add(line.trim())
} else {
break
}
}
}
return hosts
'''
]
]
]
])
])
pipeline {
agent any
stages {
stage('Build') {
steps {
script {
echo "Host:::: ${params.Host}"
}
}
}
}
}
Update
When you are calling a private repo, you need to send a Basic Auth header with the access token. So use the following groovy script instead.
def accessToken = "ACCESS_TOKEN".bytes.encodeBase64().toString()
def get = new URL("https://raw.githubusercontent.com/xxxx/something/hosts").openConnection();
get.setRequestProperty("authorization", "Basic " + accessToken)
def content = get.getInputStream().getText()

Isolation between kubernetes.admission policies in OPA

I use the vanilla Open Policy Agent as a deployment on Kubernetes for handling admission webhooks.
The behavior of multiple policies evaluation is not clear to me, see this example:
## policy-1.rego
package kubernetes.admission
check_namespace {
# evaluate to true
namespaces := {"namespace1"}
namespaces[input.request.namespace]
}
check_user {
# evaluate to false
users := {"user1"}
users[input.request.userInfo.username]
}
allow["yes - user1 and namespace1"] {
check_namespace
check_user
}
.
## policy-2.rego
package kubernetes.admission
check_namespace {
# evaluate to false
namespaces := {"namespace2"}
namespaces[input.request.namespace]
}
check_user {
# evaluate to true
users := {"user2"}
users[input.request.userInfo.username]
}
allow["yes - user2 and namespace12] {
check_namespace
check_user
}
.
## main.rego
package system
import data.kubernetes.admission
main = {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": response,
}
default uid = ""
uid = input.request.uid
response = {
"allowed": true,
"uid": uid,
} {
reason = concat(", ", admission.allow)
reason != ""
}
else = {"allowed": false, "uid": uid}
.
## example input
{
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"request": {
"namespace": "namespace1",
"userInfo": {
"username": "user2"
}
}
}
.
## Results
"allow": [
"yes - user1 and namespace1",
"yes - user2 and namespace2"
]
It seems that all of my policies are being evaluated as just one flat file, but i would expect that each policy will be evaluated independently from the others
What am I missing here?
Files don't really mean anything to OPA, but packages do. Since both of your policies are defined in the kubernetes.admission module, they'll essentially be appended together as one. This works in your case only due to one of the check_user and check_namespace respectively evaluating to undefined given your input. If they hadn't you would see an error message about conflict, since complete rules can't evalutate to different results (i.e. allow can't be both true and false).
If you rather use a separate package per policy, like, say kubernetes.admission.policy1 and kubernetes.admission.policy2, this would not be a concern. You'd need to update your main policy to collect an aggregate of the allow rules from all of your policies though. Something like:
reason = concat(", ", [a | a := data.kubernetes.admission[policy].allow[_]])
This would iterate over all the sub-packages in kubernetes.admission and collect the allow rule result from each. This pattern is called dynamic policy composition, and I wrote a longer text on the topic here.
(As a side note, you probably want to aggregate deny rules rather than allow. As far as I know, clients like kubectl won't print out the reason from the response unless it's actually denied... and it's generally less useful to know why something succeeded rather than failed. You'll still have the OPA decision logs to consult if you want to know more about why a request succeeded or failed later).

How to setup build number as a pipeline id in fastlane?

This is what I currently have in Fastfile:
def build(target_name)
cocoapods
cert
sigh
if ENV['CI_PIPELINE_ID']
increment_build_number(build_number: "#{ENV['CI_PIPELINE_ID']}")
end
build_app(
scheme: target_name,
workspace: WORKSPACE_FILE_PATH,
clean: true,
output_directory: OUTPUT_PATH,
output_name: target_name + '.ipa',
export_options: {
provisioningProfiles: {
BETA_BUNDLE_IDENTIFIER => BETA_PROVISIONING_PROFILE,
DEMO_BUNDLE_IDENTIFIER => DEMO_PROVISIONING_PROFILE,
DEV_BUNDLE_IDENTIFIER => DEV_PROVISIONING_PROFILE
}
}
)
end
But this code ends up with email from Fabric like this:
v3.3.21 (116)
instead of:
v3.3.21 (11741)
Why it doesn't assign pipeline id to build number?
It looks like it doesn't get inside if statement. Is it possible that CI_PIPELINE_ID variable is not visible for runner?

Handling attributes in InSpec

I was trying to create some basic inspec tests to validate a set of HTTP URLs. The way I started is like this -
control 'http-url-checks' do
impact 1.0
title 'http-url-checks'
desc '
Specify the URLs which need to be up and working.
'
tag 'http-url-checks'
describe http('http://example.com') do
its('status') { should eq 200 }
its('body') { should match /abc/ }
its('headers.name') { should eq 'header' }
end
describe http('http://example.net') do
its('status') { should eq 200 }
its('body') { should match /abc/ }
its('headers.name') { should eq 'header' }
end
end
We notice that the URLs are hard-coded in the controls and isn't a lot of fun. I'd like to move them to some 'attributes' file of some sort and loop through them in the control file.
My attempt was to use the 'files' folder structure inside the profile.I created a file - httpurls.yml and had the following content in it -
- url: http://example.com
- url: http://example.net
..and in my control file, I had the construct -
my_urls = yaml(content: inspec.profile.file('httpurls.yml')).params
my_urls.each do |s|
describe http(s['url']) do
its('status') { should eq 200 }
end
end
However, when I execute the compliance profile, I get an error - 'httpurls.yml not found' (not sure about the exact error message though though). The following is the folder structure I had for my compliance profile.
What I am doing wrong?
Is there a better way to achieve what I am trying to do?
The secret is to use profile attributes, as defined near the bottom of this page:
https://www.inspec.io/docs/reference/profiles/
First, create a profile attributes YML file. I name mine profile-attribute.yml.
Second, put your array of values in the YML file, like so:
urls:
- http://example.com
- http://example.net
Third, create an attribute at the top of your InSpec tests:
my_urls = attribute('urls', description: 'The URLs that I am validating.')
Fourth, use your attribute in your InSpec test:
my_urls.each do |s|
describe http(s['url']) do
its('status') { should eq 200 }
end
end
Finally, when you call your InSpec test, point to your YML file using --attrs:
inspec exec mytest.rb --reporter=cli --attrs profile-attribute.yml
There is another way to do this using files (instead of the profile attributes and the --attrs flag). You can use JSON or YAML.
First, create the JSON and/or YAML file and put them in the files directory. A simple example of the JSON file might look like this:
{
"urls": ["https://www.google.com", "https://www.apple.com"]
}
And a simple example of the YAML file might look like this:
urls:
- https://www.google.com
- https://www.apple.com
Second, include code at the top of your InSpec file to read and parse the JSON and/or YAML, like so:
jsoncontent = inspec.profile.file("tmp.json")
jsonparams = JSON.parse(jsoncontent)
jsonurls = jsonparams['urls']
yamlcontent = inspec.profile.file("tmp.yaml")
yamlparams = YAML.load(yamlcontent)
yamlurls = yamlparams['urls']
Third, use the variables in your InSpec tests, like so:
jsonurls.each do |jsonurl|
describe http(jsonurl) do
puts "json url is " + jsonurl
its('status') { should eq 200 }
end
end
yamlurls.each do |yamlurl|
describe http(yamlurl) do
puts "yaml url is " + yamlurl
its('status') { should eq 200 }
end
end
(NOTE: the puts line is for debugging.)
The result is what you would expect:
json url is https://www.google.com
json url is https://www.apple.com
yaml url is https://www.google.com
yaml url is https://www.apple.com
Profile: InSpec Profile (inspec-file-test)
Version: 0.1.0
Target: local://
http GET on https://www.google.com
✔ status should eq 200
http GET on https://www.apple.com
✔ status should eq 200
http GET on https://www.google.com
✔ status should eq 200
http GET on https://www.apple.com
✔ status should eq 200

Custom WebserviceUserProvider and FOSAuthServerBundle

I'm using an SF2.8 API based on this tutorial https://gist.github.com/tjamps/11d617a4b318d65ca583 except i'm using MongoDB.
When a FOSUser (created on my DB A with oAuth tables) is connected to my API, i don't have all infos what i need because there are an other database.
I found a SF doc which explain how to custom symfony default authentication by replacing the default FOS User provider with a custom user provider.
So I decided to create the same architecture on this doc : http://symfony.com/doc/2.8/security/custom_provider.html
Before asking my oAuth token in /oauth/v2/token by HTTP Post request, my overrided loadUserByUsername method call an external API and instanciate a WebserviceUser which contain a user companies's collection in addition to having basic fields like username, password, salt etc. needed to connect.
After loadUserByUsername call, oAuth try to flush the generated accesstoken to the connected user into DB, but an exception is thrown because the User document to persist in AccessToken document is a AppBundle\Security\User\WebserviceUser instead of AppBundle\Document\User (child of FOSUser Document) so the mapping fail while persisting.
I got this :
The class 'AppBundle\Security\User\WebserviceUser' was not found in the chain configured namespaces AppBundle\Document, FOS\UserBundle\Document, FOS\OAuthServerBundle\Document (500 Internal Server Error)
Did I do something wrong ?
EDIT : My new loadUserByUsername method :
public function loadUserByUsername($username)
{
$apiUser = $this->manager->getRepository('AppBundle:User')->findOneByUsername($username);
if (is_null($apiUser)) {
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
$userData = $this->client->httpGetList("user", ['filter_usrMail' => $apiUser->getEmail()]);
$userData = $userData[array_keys($userData)[0]];
$companies = isset($userData['usrCompany']) ? $userData['usrCompany'] : [];
if ($userData) {
$role = isset($userData['usrRole']) ? [$userData['usrRole']] : ['ROLE_USER'];
$user = new WebserviceUser($apiUser->getUsername(), $apiUser->getPassword(), $apiUser->getSalt(), $apiUser->getRoles());
$user->setCompanies($companies);
$user->setApiUsername($apiUser->getUsername());
return $user;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
fos_oauth_server yml conf :
db_driver: mongodb
client_class: AppBundle\Document\Client
access_token_class: AppBundle\Document\AccessToken
refresh_token_class: AppBundle\Document\RefreshToken
auth_code_class: AppBundle\Document\AuthCode
service:
user_provider: app.webservice_user_provider
services.yml conf :
app.webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
arguments: ['#my_communicator.client', '#session', '#doctrine.odm.mongodb.document_manager', '%session_refresh_ttl%']
security.yml conf :
encoders:
AppBundle\Security\User\WebserviceUser: bcrypt
FOS\UserBundle\Model\UserInterface: bcrypt
providers:
webservice:
id: app.webservice_user_provider
#in_memory:
# memory: ~
fos_userbundle:
id: fos_user.user_provider.username
role_hierarchy:
ROLE_ADMIN: ROLE_USER
firewalls:
oauth_token: # Everyone can access the access token URL.
pattern: ^/oauth/v2/token
security: false
api:
pattern: ^\/api(?!\/doc|\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients)) # All URLs are protected (except api doc and api create account)
fos_oauth: true # OAuth2 protected resource
stateless: true # Do no set session cookies
anonymous: false # Anonymous access is not allowed
access_denied_handler: app.listener.access_denied.handler
apidoc:
pattern: ^\/api\/doc
anonymous: false
security: false
access_control:
- { path: ^\/api\/v[0-9][\.0-9]*\/(?!(admin|core)), roles: ROLE_USER }
- { path: ^\/api\/v[0-9][\.0-9]*\/(admin|core)(?!(\/createaccount|\/clients)), roles: ROLE_ADMIN }
- { path: ^\/api\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients), roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, localhost, ::1] }
- { path: ^\/api\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients), roles: ROLE_NO_ACCESS }
access_decision_manager:
strategy: unanimous