Keeping track of user ID with custom authentication - sql

I am currently making an app on apex.oracle.com, and I've been trying to solve this for a couple hours now, but I have no idea how to.
Alright, so basically my application has custom authentication based on a user table I created inside of my application. Therefore, it seems to render useless most APEX_UTIL functions to retrieve info on the current user. The issue is, I am trying to find a way to store the user's numeric ID from my table in the session, so I could retrieve it directly in the queries throughout my application, in order to do something like WHERE id = :MEMBER_ID instead of WHERE UPPER(username) = UPPER(:APP_USER).
Now, the way I attempted to do this is by creating a Post Authentication procedure that retrieves the user ID based on the username, and stores that value in the session using APEX_UTIL.SET_SESSION_STATE( p_name => 'MEMBER_ID', p_value => member_id ). However, it seems that SET_SESSION_STATE is unable to create custom session values or something, returning an ERR-1002 every time I use a value name that isn't specifically mentioned in the documentation.
I am a total newbie to APEX so I am probably unaware of something, however I have done many searches, but I could not find anything specifically related to my issue.
Thanks a lot if you can help.

You're trying to store a value into an item, whether page or application level. This requires that the item with that name exists in one of those scopes. So, do you have an item somewhere that is called MEMBER_ID?
I'd suggest you create one in the application scope. Go through Shared Components > Application items. Once created, you should be able to assign a value either through apex_util.set_session_state or with bind variable syntax eg :MEMBER_ID := somevariable;

There are a number of ways you can do this. Some have already been suggested in other answers.
Application Item (as per Tom's answer)
PL/SQL package global (as per hol's answer) - although you'd have to reset it for each call (e.g. by adding code to the application's Security Attribute Initialization PL/SQL Code and clearing it by adding code to Cleanup PL/SQL Code).
Globally Accessible Context - this method, while a little more complex, has some benefits especially for security and debugging. I've described it here: http://jeffkemponoracle.com/2013/02/28/apex-and-application-contexts/, but basically:
Create a globally accessible context:
CREATE OR REPLACE CONTEXT MY_CONTEXT USING MY_PACKAGE ACCESSED GLOBALLY;
In the post-authentication procedure (in the database package MY_PACKAGE), you can store the data you wish to keep track of, e.g.
DBMS_SESSION.set_context
(namespace => 'MY_CONTEXT'
,attribute => 'MEMBER_ID'
,value => '12345whatever'
,client_id => v('APP_USER') || ':' || v('APP_SESSION'));
(note the caveats in my blog article and the subsequent comments from others about CLIENT_IDENTIFIER not being reliably set at the post-auth stage)
In your views, code, etc. you can access the MEMBER_ID by simply referring to SYS_CONTEXT('MY_CONTEXT','MEMBER_ID').

Related

APEX page process using a DB package

I am using APEX 5.1.4
Know this is probably one of those very easy answers - just not sure what script is used in a Page Process to call a DB package. Know I need to pass the parameters and call the specific part of the package - just not sure what that page process script should be.
Need to pass in the values from page 3 - :P3_USER_ID (which is the user's email address) and the :P3_PASSWORD. The DB package is called PSPRT_AUTH_PKG and the part of the package is CREATE_ACCOUNT. Thanks!!
Think I found the answer...
use this in the page processing as PL/SQL code...
psprt_auth_pkg.create_account(:P3_USERNAME, :P3_PASSWORD);
Something like (you'll need to correct my made-up parameter names):
PSPRT_AUTH_PKG.CREATE_ACCOUNT
( p_user_id => :P3_USER_ID
, p_password => :P3_PASSWORD
);

#auth.requires_permission not working ver 2

Good day to all web2py experts!
I can't find a way on how to use the web2py Decorators
#auth.requires_permission('read','person')
def f(): ....
in the pdf manual it says that:
prevents visitors from accessing the function f unless the visitor is a member
of a group whose members have permissions to "read" records of table
"person". If the visitor is not logged in, the visitor gets directed to a login
page (provided by default by web2py). web2py also supports components,
i.e. actions which can be loaded in a view and interact with the visitor via
Ajax without re-loading the entire page. This is done via a LOAD helper which
allows very modular design of applications; it is discussed in chapter 3 in the
context of the wiki and, in some detail, in the last chapter of this book.
This 5th edition of the book describes web2py 2.4.1 and later versions
In my case:
I have list of groups: Admin_Tier_1, Admin_Tier_2, Admin_Tier_3
Admin_Tier_1 - has the highest authority to access all features like adding a school year, set a school year etc.
Admin_Tier_2 - has the authority to add students etc
Admin_Tier_3 - its the lowest level of authority that can only add fines to the students (Organization Officers)
now I use the Decorator code like this:
#auth.requires_permission('Admin_Tier_1','student_list')
def add(): ....
now I login the account of the Chairman which registered in the auth_membership as Admin_Tier_1. Then I click the link "List of Students" which redirect to add(): function but the system returned a message:
Not Authorized
Insufficient privileges
The auth.requires() method can take a callable rather than a boolean value as the condition, and this is preferable when it is expensive to generate the boolean value (otherwise, the boolean value is generated whenever the controller is accessed, even if the particular decorated function is not the one being called). So, to avoid calling auth.has_membership unnecessarily, you can do:
#auth.requires(lambda: auth.has_membership('Admin_Tier_1') or
auth.has_membership('Admin_Tier_2'))
Now the two auth.has_membership calls will only be made when the actual function being decorated is called.
Also, if you need to check a larger number of roles, you can do something like:
#auth.requires(lambda: any([auth.has_membership(r) for r in ['list', 'of', 'roles']))
Problem solved:
#auth.requires(auth.has_membership('Admin_Tier_1') or auth.has_membership('Admin_Tier_2'))
source here.
Whenever I access the page if the user belong to the group of Admin_Tier_3 the system block the acess and redirect it to "/default/user/not_authorized" page :)

How to prevent DataTables from displaying or hiding columns on the basis of an obsolete saved state

I have a table driven by DataTables 1.10. Filtering is turned on. When I talk about "doing a search" below, I'm talking about using the filtering function of this table.
Description of the Problem
Everything works fine with stateSave off. However, when stateSave is on, the following happens:
Alice logs in as admin. Because admin has all privileges, when she does a search through articles, she can see all articles. Because some articles are published and some are unpublished the table has a column that show which are published and which are not. So far so good.
Bob, a random user, accesses the site. Random users cannot ever see unpublished articles so the table hides the column that shows publication status. So far so good.
Alice logs out. She now accesses the site like a random user. So she should see exactly what Bob sees. However, when she does a search she still sees the column that indicates publication status.
(Note: The issue I'm discussing here is purely one of user interface. The server ensures that unprivileged users cannot ever get a record for an unpublished article. The problem though is that the additional column gives unpriviledged users information that they do not need. They can only see published articles in their search so they don't need to see that every article they get in a search is published.)
The code that configures the datatable hides the publication column by doing something like this:
var columnDefs = [];
if (!privileged) {
columnDefs.push({
targets: [1],
orderable: false,
visible: false
});
}
columnDefs is passed to DataTables as the columnDefs option.
Technical Reason for the Problem
The problem is that DataTables store things like column visibility into the state it saves into localStorage. So when Alice logs out and makes a search again as an unprivileged user, even though the value of columnDefs is correct, it is overwritten by the saved state. That state was stored when Alice was an admin, and it declared the publication column to be visible, so it remains visible even when Alice is accessing the site as an unprivileged user.
What I want is for users to benefit from the saved state but avoid having this state carry over when the user's privileges change.
Caveats:
I don't want to use sessionStorage because I want the state to persist between browser closings, but sessionStorage is cleared when the browser is closed.
I cannot use the session cookie assigned by the server to detect logins and logouts because it is HTTP only. Besides, privileges could change for other reasons.
I do not want to arbitrarily set an expiration time on the saved state.
The solution I've settled on is to use an additional field in the saved data to know when the conditions I care about have changed. This is a field whose value changes depending on the privileges that the user currently has. For instance, because in the case I described here, I decide to hide or show a column on the basis of a variable named priviledged (which is initialized from data provided by the server), it could be as simple as:
var token = privileged;
Then I set stateSaveParams to record the token when the state is saved:
stateSaveParams: function (settings, data) {
data.myapp_token = token;
}
The prefix myapp_ is just there to avoid possible collisions with DataTable's own fields.
I set stateLoadParams so that if the current value of token differs from what has been recorded before, the state is cleared:
stateLoadParams: function (settings, data) {
if (data.myapp_token !== token) {
this.api().state.clear(); // Clears the state.
return false; // Tells DataTables to not use the state that was stored.
}
// This return is here to keep the IDE happy but does not do anything special.
return undefined;
},
I've just set token to the single condition I've shown in my question (privileged) in this example but in production I use a combination of variables plus a local version number so I can bump the value of token as needed if I do something that requires clearing the state but cannot be detected just as a privilege change.

Woocommerce select email address destination only for new order notification(admin) through form checkout it's possible?

Scenery: 1 store and 2 locations. I already have select field form (checkout) where the customer select which store is closest to your location, eg: city A or city B.
Now I need that the new order notification (admin) sent ONLY to e-mail the city shop chosen in form.
Seems to me that all this operations occurs near: class-wc-order, class-wc-checkout.php
But I belive that exist that do it only using filters
Any ideas?
Thanks advance.
$_SESSION Vars on WooCommerce work's fine.
Well not the fine way, but also not worst too!
Create a jquery ($.ajax) function to call one php script external file and register the session variable ($_SESSION).
In my case this php is in (eg: public_html/loadvar.php), this file receive $_POST variables through ajax (in my case: when the user select your office location prefered, this value eg: emailA or emailB according office selected (onChange event) )
I used this session variable to store the alternative email addresses. (not admin_email default)
In (class-wc-checkout.php) , retrieve these values (session variable) eg: email1 or email2... previously selected on form checkout, and overriding the "get_option( 'admin_email' )" by the $_SESSION value
Now, users can choice the office that prefer, and only the selected office will receive the quote request
Note: I only can use this session vars on woocommerce configuring wp_unregister_GLOBALS() on wp-includes/load.php or disable it commenting this function.
This worked (not is state-of-art code) maybe dirty code but can't need deep coding, obviously not "strict patch" this will not survive an update
Thats all folks

How to use a hidden page item within an SQL report query without submitting

Using: Oracle ApEx 3.0.1
I have a SQL report region that contains a hidden page item as part of the "where clause". My problem is, based on a value entered by the user, I need to assign this value entered to my hidden item, so that it can be used within the where condition of my SQL but this would need to be done without actually submitting the form.
At the moment, I can set the value via an on-demand process but my SQL is still not returning any values as the hidden page item within the query is not set (as page has not been submitted).
I am not sure how to do this and whether in actual fact, this is possible to do, without having submitted the page.
Since you are on Apex 3, you don't have dynamic actions, but that doesn't hinder so much.
I've set up an example on apex.oracle.com. To get in the workspace, use workspace 'tompetrusbe' + apex_demo / demo.
There is a dynamic action there, which can do the work too, but i've disabled it.
What you need to make it work:
an ajax callback process, with the following line:
apex_util.set_session_state('P2_PAR_ENAME', apex_application.g_x01);
Give your report region a static id, i called mine 'report_emp'. This so i can easily retrieve it.
In the javascript region of the page, you then need to call the app process, and then refresh the region. Also bind the event which needs to trigger this action. I've done it here through the onchange event of the parameter textfield.
function refresh_report(oTrgEl){
//alert('refresh: ' + $v(oTrgEl));
//calling the application process which sets the session state of P2_PAR_ENAME
var oGet = new htmldb_Get(null, &APP_ID., 'APPLICATION_PROCESS=set_session_state', &APP_PAGE_ID.);
oGet.addParam("x01", $v(oTrgEl));
oGet.get(); //the app process just sets something, it returns nothing
//refresh the report region
$("#report_emp").trigger("apexrefresh");
};
function bind_events(){
//call this onload
$("#P2_PAR_ENAME").change(function(){refresh_report(this);});
};
In the query of the report i use where ename like '%'||UPPER(:P2_PAR_ENAME)||'%'.
When you type (for example) 'bl' in, and tab out (to trigger the onchange), the region will refresh and will be filtered.
You'll just need to adapt to your solution :)